diff options
author | Alexander Tseung <alextsg@gmail.com> | 2018-01-30 10:22:52 +0800 |
---|---|---|
committer | Alexander Tseung <alextsg@gmail.com> | 2018-01-30 10:22:52 +0800 |
commit | ecc39c5a7abd8c8794d5565c1bc7d213d3514d61 (patch) | |
tree | 746f0a2bada5d8cd6636789d3c498c1ef13901fb /ui/app/components | |
parent | d905b86ba775aad888d1dfd22257958fd9415909 (diff) | |
parent | b05d21b1ba308bdb5b758d53dd79593a7a2bf26e (diff) | |
download | tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.gz tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.bz2 tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.lz tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.xz tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.tar.zst tangerine-wallet-browser-ecc39c5a7abd8c8794d5565c1bc7d213d3514d61.zip |
Merge branch 'uat' of https://github.com/MetaMask/metamask-extension into cb-254
Diffstat (limited to 'ui/app/components')
31 files changed, 731 insertions, 548 deletions
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 0d3b43ae9..f031f2446 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -33,15 +33,28 @@ function mapDispatchToProps (dispatch) { toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()), showAccountDetail: address => { dispatch(actions.showAccountDetail(address)) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, lockMetamask: () => { dispatch(actions.lockMetamask()) - dispatch(actions.displayWarning(null)) + dispatch(actions.hideWarning()) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, - showNewAccountModal: () => { - dispatch(actions.showModal({ name: 'NEW_ACCOUNT' })) + showConfigPage: () => { + dispatch(actions.showConfigPage()) + dispatch(actions.hideSidebar()) + dispatch(actions.toggleAccountMenu()) + }, + showNewAccountPage: (formToSelect) => { + dispatch(actions.showNewAccountPage(formToSelect)) + dispatch(actions.hideSidebar()) + dispatch(actions.toggleAccountMenu()) + }, + showInfoPage: () => { + dispatch(actions.showInfoPage()) + dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, } @@ -51,7 +64,7 @@ AccountMenu.prototype.render = function () { const { isAccountMenuOpen, toggleAccountMenu, - showNewAccountModal, + showNewAccountPage, lockMetamask, history, } = this.props @@ -73,15 +86,12 @@ AccountMenu.prototype.render = function () { h('div.account-menu__accounts', this.renderAccounts()), h(Divider), h(Item, { - onClick: showNewAccountModal, + onClick: () => showNewAccountPage('CREATE'), icon: h('img', { src: 'images/plus-btn-white.svg' }), text: 'Create Account', }), h(Item, { - onClick: () => { - toggleAccountMenu() - history.push(IMPORT_ACCOUNT_ROUTE) - }, + onClick: () => showNewAccountPage('IMPORT'), icon: h('img', { src: 'images/import-account.svg' }), text: 'Import Account', }), diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js index d14aa675f..d591ab455 100644 --- a/ui/app/components/balance-component.js +++ b/ui/app/components/balance-component.js @@ -40,7 +40,7 @@ BalanceComponent.prototype.render = function () { // style: {}, // }), h(Identicon, { - diameter: 45, + diameter: 50, address: token && token.address, network, }), @@ -94,7 +94,8 @@ BalanceComponent.prototype.renderFiatValue = function (formattedBalance) { } BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) { - if (fiatDisplayNumber === 'N/A') return null + const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 + if (shouldNotRenderFiat) return null return h('div.fiat-amount', { style: {}, diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index f44d86045..f70208625 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -40,7 +40,7 @@ CoinbaseForm.prototype.render = function () { }, 'Continue to Coinbase'), h('button.btn-red', { - onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), + onClick: () => props.dispatch(actions.goHome()), }, 'Cancel'), ]), ]) diff --git a/ui/app/components/currency-input.js b/ui/app/components/currency-input.js index 66880091f..6f7862e51 100644 --- a/ui/app/components/currency-input.js +++ b/ui/app/components/currency-input.js @@ -50,10 +50,18 @@ function sanitizeValue (value) { CurrencyInput.prototype.handleChange = function (newValue) { const { onInputChange } = this.props + const { value } = this.state - this.setState({ value: sanitizeValue(newValue) }) + let parsedValue = newValue + const newValueLastIndex = newValue.length - 1 - onInputChange(sanitizeValue(newValue)) + if (value === '0' && newValue[newValueLastIndex] === '0') { + parsedValue = parsedValue.slice(0, newValueLastIndex) + } + + const sanitizedValue = sanitizeValue(parsedValue) + this.setState({ value: sanitizedValue }) + onInputChange(sanitizedValue) } // If state.value === props.value plus a decimal point, or at least one @@ -90,6 +98,6 @@ CurrencyInput.prototype.render = function () { size: valueToRender.length * inputSizeMultiplier, readOnly, onChange: e => this.handleChange(e.target.value), - ref: inputRef, + ref: inputRef, }) } diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js index 58326b13c..f97ac0691 100644 --- a/ui/app/components/dropdowns/components/account-dropdowns.js +++ b/ui/app/components/dropdowns/components/account-dropdowns.js @@ -199,7 +199,7 @@ class AccountDropdowns extends Component { {}, menuItemStyles, ), - onClick: () => actions.showNewAccountModal(), + onClick: () => actions.showNewAccountPageCreateForm(), }, [ h( @@ -228,7 +228,7 @@ class AccountDropdowns extends Component { actions.hideSidebar() } }, - onClick: () => actions.showImportPage(), + onClick: () => actions.showNewAccountPageImportForm(), style: Object.assign( {}, menuItemStyles, @@ -457,9 +457,7 @@ const mapDispatchToProps = (dispatch) => { identity, })) }, - showNewAccountModal: () => { - dispatch(actions.showModal({ name: 'NEW_ACCOUNT' })) - }, + showNewAccountPageCreateForm: () => dispatch(actions.showNewAccountPage({ form: 'CREATE' })), showExportPrivateKeyModal: () => { dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' })) }, @@ -467,7 +465,7 @@ const mapDispatchToProps = (dispatch) => { dispatch(actions.showAddTokenPage()) }, addNewAccount: () => dispatch(actions.addNewAccount()), - showImportPage: () => dispatch(actions.showImportPage()), + showNewAccountPageImportForm: () => dispatch(actions.showNewAccountPage({ form: 'IMPORT' })), showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)), }, } diff --git a/ui/app/components/mascot.js b/ui/app/components/mascot.js index 973ec2cad..3b0d3e31b 100644 --- a/ui/app/components/mascot.js +++ b/ui/app/components/mascot.js @@ -7,13 +7,13 @@ const debounce = require('debounce') module.exports = Mascot inherits(Mascot, Component) -function Mascot () { +function Mascot ({width = '200', height = '200'}) { Component.call(this) this.logo = metamaskLogo({ followMouse: true, pxNotRatio: true, - width: 200, - height: 200, + width, + height, }) this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000) diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js index 4bf671834..c1f7a3236 100644 --- a/ui/app/components/modals/account-details-modal.js +++ b/ui/app/components/modals/account-details-modal.js @@ -62,12 +62,12 @@ AccountDetailsModal.prototype.render = function () { h('div.account-modal-divider'), - h('button.btn-clear', { + h('button.btn-clear.account-modal__button', { onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }), }, 'View account on Etherscan'), // Holding on redesign for Export Private Key functionality - h('button.btn-clear', { + h('button.btn-clear.account-modal__button', { onClick: () => showExportPrivateKeyModal(), }, 'Export private key'), diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js index d735983f9..74a7a847e 100644 --- a/ui/app/components/modals/buy-options-modal.js +++ b/ui/app/components/modals/buy-options-modal.js @@ -69,7 +69,7 @@ BuyOptions.prototype.render = function () { // h('div.buy-modal-content-option', {}, [ // h('div.buy-modal-content-option-title', {}, 'Shapeshift'), // h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'), - // ]), + // ]),, this.renderModalContentOption( 'Direct Deposit', diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js new file mode 100644 index 000000000..532d66653 --- /dev/null +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -0,0 +1,184 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const actions = require('../../actions') +const networkNames = require('../../../../app/scripts/config.js').networkNames +const ShapeshiftForm = require('../shapeshift-form') + +const DIRECT_DEPOSIT_ROW_TITLE = 'Directly Deposit Ether' +const DIRECT_DEPOSIT_ROW_TEXT = `If you already have some Ether, the quickest way to get Ether in +your new wallet by direct deposit.` +const COINBASE_ROW_TITLE = 'Buy on Coinbase' +const COINBASE_ROW_TEXT = `Coinbase is the world’s most popular way to buy and sell bitcoin, +ethereum, and litecoin.` +const SHAPESHIFT_ROW_TITLE = 'Deposit with ShapeShift' +const SHAPESHIFT_ROW_TEXT = `If you own other cryptocurrencies, you can trade and deposit Ether +directly into your MetaMask wallet. No Account Needed.` +const FAUCET_ROW_TITLE = 'Test Faucet' +const facuetRowText = networkName => `Get Ether from a faucet for the ${networkName}` + +function mapStateToProps (state) { + return { + network: state.metamask.network, + address: state.metamask.selectedAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + toCoinbase: (address) => { + dispatch(actions.buyEth({ network: '1', address, amount: 0 })) + }, + hideModal: () => { + dispatch(actions.hideModal()) + }, + showAccountDetailModal: () => { + dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) + }, + toFaucet: network => dispatch(actions.buyEth({ network })), + } +} + +inherits(DepositEtherModal, Component) +function DepositEtherModal () { + Component.call(this) + + this.state = { + buyingWithShapeshift: false, + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) + +DepositEtherModal.prototype.renderRow = function ({ + logo, + title, + text, + buttonLabel, + onButtonClick, + hide, + className, + hideButton, + hideTitle, + onBackClick, + showBackButton, +}) { + if (hide) { + return null + } + + return h('div', { + className: className || 'deposit-ether-modal__buy-row', + }, [ + + onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', { + onClick: onBackClick, + }, [ + + h('i.fa.fa-arrow-left.cursor-pointer'), + + ]), + + h('div.deposit-ether-modal__buy-row__logo', [logo]), + + h('div.deposit-ether-modal__buy-row__description', [ + + !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), + + h('div.deposit-ether-modal__buy-row__description__text', [text]), + + ]), + + !hideButton && h('div.deposit-ether-modal__buy-row__button', [ + h('button.deposit-ether-modal__deposit-button', { + onClick: onButtonClick, + }, [buttonLabel]), + ]), + + ]) +} + +DepositEtherModal.prototype.render = function () { + const { network, toCoinbase, address, toFaucet } = this.props + const { buyingWithShapeshift } = this.state + + const isTestNetwork = ['3', '4', '42'].find(n => n === network) + const networkName = networkNames[network] + + return h('div.deposit-ether-modal', {}, [ + + h('div.deposit-ether-modal__header', [ + + h('div.deposit-ether-modal__header__title', ['Deposit Ether']), + + h('div.deposit-ether-modal__header__description', [ + 'To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet.', + ]), + + h('div.deposit-ether-modal__header__close', { + onClick: () => { + this.setState({ buyingWithShapeshift: false }) + this.props.hideModal() + }, + }), + + ]), + + h('div.deposit-ether-modal__buy-rows', [ + + this.renderRow({ + logo: h('img.deposit-ether-modal__buy-row__eth-logo', { src: '../../../images/eth_logo.svg' }), + title: DIRECT_DEPOSIT_ROW_TITLE, + text: DIRECT_DEPOSIT_ROW_TEXT, + buttonLabel: 'View Account', + onButtonClick: () => this.goToAccountDetailsModal(), + hide: buyingWithShapeshift, + }), + + this.renderRow({ + logo: h('i.fa.fa-tint.fa-2x'), + title: FAUCET_ROW_TITLE, + text: facuetRowText(networkName), + buttonLabel: 'Get Ether', + onButtonClick: () => toFaucet(network), + hide: !isTestNetwork || buyingWithShapeshift, + }), + + this.renderRow({ + logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', { + src: '../../../images/coinbase logo.png', + }), + title: COINBASE_ROW_TITLE, + text: COINBASE_ROW_TEXT, + buttonLabel: 'Continue to Coinbase', + onButtonClick: () => toCoinbase(address), + hide: isTestNetwork || buyingWithShapeshift, + }), + + this.renderRow({ + logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', { + src: '../../../images/shapeshift logo.png', + }), + title: SHAPESHIFT_ROW_TITLE, + text: SHAPESHIFT_ROW_TEXT, + buttonLabel: 'Buy with Shapeshift', + onButtonClick: () => this.setState({ buyingWithShapeshift: true }), + hide: isTestNetwork, + hideButton: buyingWithShapeshift, + hideTitle: buyingWithShapeshift, + onBackClick: () => this.setState({ buyingWithShapeshift: false }), + showBackButton: this.state.buyingWithShapeshift, + className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy', + }), + + buyingWithShapeshift && h(ShapeshiftForm), + + ]), + ]) +} + +DepositEtherModal.prototype.goToAccountDetailsModal = function () { + this.props.hideModal() + this.props.showAccountDetailModal() +} diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js index 193755df5..422f23f44 100644 --- a/ui/app/components/modals/export-private-key-modal.js +++ b/ui/app/components/modals/export-private-key-modal.js @@ -79,11 +79,15 @@ ExportPrivateKeyModal.prototype.renderButton = function (className, onClick, lab ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) { return h('div.export-private-key-buttons', {}, [ - !privateKey && this.renderButton('btn-clear btn-cancel', () => hideModal(), 'Cancel'), + !privateKey && this.renderButton( + 'btn-cancel export-private-key__button export-private-key__button--cancel', + () => hideModal(), + 'Cancel' + ), (privateKey - ? this.renderButton('btn-clear', () => hideModal(), 'Done') - : this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Show') + ? this.renderButton('btn-clear export-private-key__button', () => hideModal(), 'Done') + : this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Confirm') ), ]) diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js index fa3ad0b1e..56c7ba299 100644 --- a/ui/app/components/modals/hide-token-confirmation-modal.js +++ b/ui/app/components/modals/hide-token-confirmation-modal.js @@ -58,12 +58,12 @@ HideTokenConfirmationModal.prototype.render = function () { ]), h('div.hide-token-confirmation__buttons', {}, [ - h('button.btn-clear', { + h('button.btn-cancel.hide-token-confirmation__button', { onClick: () => hideModal(), }, [ 'CANCEL', ]), - h('button.btn-clear', { + h('button.btn-clear.hide-token-confirmation__button', { onClick: () => hideToken(address), }, [ 'HIDE', diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 2ff6accaa..afb2a2175 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -9,6 +9,7 @@ const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-n // Modal Components const BuyOptions = require('./buy-options-modal') +const DepositEtherModal = require('./deposit-ether-modal') const AccountDetailsModal = require('./account-details-modal') const EditAccountNameModal = require('./edit-account-name-modal') const ExportPrivateKeyModal = require('./export-private-key-modal') @@ -73,6 +74,37 @@ const MODALS = { }, }, + DEPOSIT_ETHER: { + contents: [ + h(DepositEtherModal, {}, []), + ], + mobileModalStyle: { + width: '100%', + height: '100%', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)', + top: '0', + display: 'flex', + }, + laptopModalStyle: { + width: '900px', + maxWidth: '900px', + top: 'calc(10% + 10px)', + left: '0', + right: '0', + margin: '0 auto', + boxShadow: '0 0 6px 0 rgba(0,0,0,0.3)', + borderRadius: '8px', + transform: 'none', + }, + contentStyle: { + borderRadius: '8px', + }, + }, + EDIT_ACCOUNT_NAME: { contents: [ h(EditAccountNameModal, {}, []), diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 5a8d0763d..3f147159b 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -39,7 +39,6 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }), - h('i.fa.fa-caret-down.network-caret'), ]) } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' @@ -85,12 +84,8 @@ Network.prototype.render = function () { backgroundColor: '#038789', // $blue-lagoon nonSelectBackgroundColor: '#15afb2', }), - h('.network-name', { - style: { - color: '#039396', - }}, - 'Main Network'), - h('i.fa.fa-caret-down.fa-lg.network-caret'), + h('.network-name', 'Main Network'), + h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) case 'ropsten-test-network': return h('.network-indicator', [ @@ -98,12 +93,8 @@ Network.prototype.render = function () { backgroundColor: '#e91550', // $crimson nonSelectBackgroundColor: '#ec2c50', }), - h('.network-name', { - style: { - color: '#ff6666', - }}, - 'Ropsten Test Net'), - h('i.fa.fa-caret-down.fa-lg.network-caret'), + h('.network-name', 'Ropsten Test Net'), + h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) case 'kovan-test-network': return h('.network-indicator', [ @@ -111,12 +102,8 @@ Network.prototype.render = function () { backgroundColor: '#690496', // $purple nonSelectBackgroundColor: '#b039f3', }), - h('.network-name', { - style: { - color: '#690496', - }}, - 'Kovan Test Net'), - h('i.fa.fa-caret-down.fa-lg.network-caret'), + h('.network-name', 'Kovan Test Net'), + h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) case 'rinkeby-test-network': return h('.network-indicator', [ @@ -124,12 +111,8 @@ Network.prototype.render = function () { backgroundColor: '#ebb33f', // $tulip-tree nonSelectBackgroundColor: '#ecb23e', }), - h('.network-name', { - style: { - color: '#e7a218', - }}, - 'Rinkeby Test Net'), - h('i.fa.fa-caret-down.fa-lg.network-caret'), + h('.network-name', 'Rinkeby Test Net'), + h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) default: return h('.network-indicator', [ @@ -140,12 +123,8 @@ Network.prototype.render = function () { }, }), - h('.network-name', { - style: { - color: '#AEAEAE', - }}, - 'Private Network'), - h('i.fa.fa-caret-down.fa-lg.network-caret'), + h('.network-name', 'Private Network'), + h('i.fa.fa-chevron-down.fa-lg.network-caret'), ]) } })(), diff --git a/ui/app/components/pages/add-token.js b/ui/app/components/pages/add-token.js index 7f0c37475..0454ee2f1 100644 --- a/ui/app/components/pages/add-token.js +++ b/ui/app/components/pages/add-token.js @@ -3,6 +3,7 @@ const Component = require('react').Component const classnames = require('classnames') const h = require('react-hyperscript') const connect = require('react-redux').connect +const R = require('ramda') const Fuse = require('fuse.js') const contractMap = require('eth-contract-metadata') const TokenBalance = require('../../components/token-balance') @@ -17,7 +18,10 @@ const fuse = new Fuse(contractList, { distance: 100, maxPatternLength: 32, minMatchCharLength: 1, - keys: ['address', 'name', 'symbol'], + keys: [ + { name: 'name', weight: 0.5 }, + { name: 'symbol', weight: 0.5 }, + ], }) // const actions = require('./actions') const actions = require('../../actions') @@ -219,9 +223,11 @@ AddTokenScreen.prototype.renderCustomForm = function () { AddTokenScreen.prototype.renderTokenList = function () { const { searchQuery = '', selectedTokens } = this.state - const results = searchQuery - ? fuse.search(searchQuery) || [] - : contractList + const fuseSearchResult = fuse.search(searchQuery) + const addressSearchResult = contractList.filter(token => { + return token.address.toLowerCase() === searchQuery.toLowerCase() + }) + const results = [...addressSearchResult, ...fuseSearchResult] return Array(6).fill(undefined) .map((_, i) => { @@ -297,12 +303,12 @@ AddTokenScreen.prototype.renderConfirmation = function () { ]), ]), h('div.add-token__buttons', [ - h('button.btn-secondary', { - onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)), - }, 'Add Tokens'), - h('button.btn-tertiary', { + h('button.btn-cancel.add-token__button', { onClick: () => this.setState({ isShowingConfirmation: false }), }, 'Back'), + h('button.btn-clear.add-token__button', { + onClick: () => addTokens(tokens).then(() => history.push(DEFAULT_ROUTE)), + }, 'Add Tokens'), ]), ]) ) @@ -347,12 +353,12 @@ AddTokenScreen.prototype.render = function () { ]), ]), h('div.add-token__buttons', [ - h('button.btn-secondary', { - onClick: this.onNext, - }, 'Next'), - h('button.btn-tertiary', { + h('button.btn-cancel.add-token__button', { onClick: () => history.goBack(), }, 'Cancel'), + h('button.btn-clear.add-token__button', { + onClick: this.onNext, + }, 'Next'), ]), ]) ) diff --git a/ui/app/components/pages/import-account/json.js b/ui/app/components/pages/import-account/json.js index 63a44b4b7..c7d232d30 100644 --- a/ui/app/components/pages/import-account/json.js +++ b/ui/app/components/pages/import-account/json.js @@ -24,14 +24,7 @@ JsonImportSubview.prototype.render = function () { const { error } = this.props return ( - h('div', { - style: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: '5px 15px 0px 15px', - }, - }, [ + h('div.new-account-import-form__json', [ h('p', 'Used by a variety of different clients'), h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), @@ -40,28 +33,35 @@ JsonImportSubview.prototype.render = function () { readAs: 'text', onLoad: this.onLoad.bind(this), style: { - margin: '20px 0px 12px 20px', + margin: '20px 0px 12px 34%', fontSize: '15px', + display: 'flex', + justifyContent: 'center', }, }), - h('input.large-input.letter-spacey', { + h('input.new-account-import-form__input-password', { type: 'password', placeholder: 'Enter password', id: 'json-password-box', onKeyPress: this.createKeyringOnEnter.bind(this), - style: { - width: 260, - marginTop: 12, - }, }), - h('button.primary', { - onClick: this.createNewKeychain.bind(this), - style: { - margin: 12, - }, - }, 'Import'), + h('div.new-account-create-form__buttons', {}, [ + + h('button.new-account-create-form__button-cancel', { + onClick: () => this.props.goHome(), + }, [ + 'CANCEL', + ]), + + h('button.new-account-create-form__button-create', { + onClick: () => this.createNewKeychain.bind(this), + }, [ + 'IMPORT', + ]), + + ]), error ? h('span.error', error) : null, ]) diff --git a/ui/app/components/pages/import-account/private-key.js b/ui/app/components/pages/import-account/private-key.js index 217e6d9f9..a236d90ee 100644 --- a/ui/app/components/pages/import-account/private-key.js +++ b/ui/app/components/pages/import-account/private-key.js @@ -4,7 +4,7 @@ const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('../../../actions') -module.exports = connect(mapStateToProps)(PrivateKeyImportView) +module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView) function mapStateToProps (state) { return { @@ -12,45 +12,49 @@ function mapStateToProps (state) { } } +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(actions.goHome()), + importNewAccount: (strategy, [ privateKey ]) => { + dispatch(actions.importNewAccount(strategy, [ privateKey ])) + }, + displayWarning: () => dispatch(actions.displayWarning(null)), + } +} + inherits(PrivateKeyImportView, Component) function PrivateKeyImportView () { Component.call(this) } -PrivateKeyImportView.prototype.componentWillUnmount = function () { - this.props.dispatch(actions.displayWarning(null)) -} - PrivateKeyImportView.prototype.render = function () { - const { error } = this.props + const { error, goHome } = this.props return ( - h('div', { - style: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: '5px 15px 0px 15px', - }, - }, [ - h('span', 'Paste your private key string here'), + h('div.new-account-import-form__private-key', [ + h('span.new-account-create-form__instruction', 'Paste your private key string here:'), - h('input.large-input.letter-spacey', { + h('input.new-account-import-form__input-password', { type: 'password', id: 'private-key-box', - onKeyPress: this.createKeyringOnEnter.bind(this), - style: { - width: 260, - marginTop: 12, - }, + onKeyPress: () => this.createKeyringOnEnter(), }), - h('button.primary', { - onClick: this.createNewKeychain.bind(this), - style: { - margin: 12, - }, - }, 'Import'), + h('div.new-account-create-form__buttons', {}, [ + + h('button.new-account-create-form__button-cancel', { + onClick: () => goHome(), + }, [ + 'CANCEL', + ]), + + h('button.new-account-create-form__button-create', { + onClick: () => this.createNewKeychain(), + }, [ + 'IMPORT', + ]), + + ]), error ? h('span.error', error) : null, ]) @@ -67,5 +71,6 @@ PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { PrivateKeyImportView.prototype.createNewKeychain = function () { const input = document.getElementById('private-key-box') const privateKey = input.value - this.props.dispatch(actions.importNewAccount('Private Key', [ privateKey ])) + + this.props.importNewAccount('Private Key', [ privateKey ]) } diff --git a/ui/app/components/pages/keychains/restore-vault.js b/ui/app/components/pages/keychains/restore-vault.js index 5573f2dd2..749da9758 100644 --- a/ui/app/components/pages/keychains/restore-vault.js +++ b/ui/app/components/pages/keychains/restore-vault.js @@ -52,7 +52,10 @@ class RestoreVaultPage extends PersistentForm { // submit this.props.createNewVaultAndRestore(password, seed) .then(() => history.push(DEFAULT_ROUTE)) - .catch(({ message }) => this.setState({ error: message })) + .catch(({ message }) => { + this.setState({ error: message }) + log.error(message) + }) } render () { diff --git a/ui/app/components/pages/settings/settings.js b/ui/app/components/pages/settings/settings.js index 537270dee..fb3f20a95 100644 --- a/ui/app/components/pages/settings/settings.js +++ b/ui/app/components/pages/settings/settings.js @@ -11,6 +11,7 @@ const { exportAsFile } = require('../../../util') const SimpleDropdown = require('../../dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { REVEAL_SEED_ROUTE } = require('../../../routes') +const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { @@ -230,18 +231,18 @@ class Settings extends Component { } render () { - const { warning } = this.props + const { warning, isMascara } = this.props return ( h('div.settings__content', [ warning && h('div.settings__error', warning), - this.renderBlockieOptIn(), this.renderCurrentConversion(), // this.renderCurrentProvider(), this.renderNewRpcUrl(), this.renderStateLogs(), this.renderSeedWords(), - this.renderOldUI(), + !isMascara && this.renderOldUI(), + this.renderBlockieOptIn(), ]) ) } @@ -257,12 +258,14 @@ Settings.propTypes = { setFeatureFlagToBeta: PropTypes.func, warning: PropTypes.string, history: PropTypes.object, + isMascara: PropTypes.bool, } const mapStateToProps = state => { return { metamask: state.metamask, warning: state.appState.warning, + isMascara: state.metamask.isMascara, } } @@ -273,7 +276,10 @@ const mapDispatchToProps = dispatch => { displayWarning: warning => dispatch(actions.displayWarning(warning)), revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()), setUseBlockie: value => dispatch(actions.setUseBlockie(value)), - setFeatureFlagToBeta: () => dispatch(actions.setFeatureFlag('betaUI', false)), + setFeatureFlagToBeta: () => { + return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')) + .then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))) + }, } } diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 8a167f7cd..e63415194 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -231,8 +231,8 @@ ConfirmSendEther.prototype.render = function () { // Main Send token Card h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ - h('button.confirm-screen-back-button', { - onClick: () => this.editTransaction(txMeta), + h('button.btn-clear.confirm-screen-back-button', { + onClick: () => editTransaction(txMeta), }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), @@ -433,7 +433,9 @@ ConfirmSendEther.prototype.onSubmit = function (event) { ConfirmSendEther.prototype.cancel = function (event, txMeta) { event.preventDefault() - this.props.cancelTransaction(txMeta) + const { cancelTransaction } = this.props + + cancelTransaction(txMeta) .then(() => this.props.history.push(DEFAULT_ROUTE)) } @@ -458,26 +460,6 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) - if (props.send.editingTransactionId) { - const { - send: { - memo, - amount: value, - gasLimit: gas, - gasPrice, - }, - } = props - const { txParams: { from, to } } = txData - txData.txParams = { - from: ethUtil.addHexPrefix(from), - to: ethUtil.addHexPrefix(to), - memo: memo && ethUtil.addHexPrefix(memo), - value: ethUtil.addHexPrefix(value), - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - } - } - // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 8087e431f..da3a92fa8 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -4,7 +4,6 @@ const { withRouter } = require('react-router-dom') const { compose } = require('recompose') const h = require('react-hyperscript') const inherits = require('util').inherits -const ethAbi = require('ethereumjs-abi') const tokenAbi = require('human-standard-token-abi') const abiDecoder = require('abi-decoder') abiDecoder.addABI(tokenAbi) @@ -305,6 +304,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () { } ConfirmSendToken.prototype.render = function () { + const { editTransaction } = this.props const txMeta = this.gatherTxMeta() const { from: { @@ -326,8 +326,8 @@ ConfirmSendToken.prototype.render = function () { // Main Send token Card h('div.confirm-screen-wrapper.flex-column.flex-grow', [ h('h3.flex-center.confirm-screen-header', [ - h('button.confirm-screen-back-button', { - onClick: () => this.editTransaction(txMeta), + h('button.btn-clear.confirm-screen-back-button', { + onClick: () => editTransaction(txMeta), }, 'EDIT'), h('div.confirm-screen-title', 'Confirm Transaction'), h('div.confirm-screen-header-tip'), @@ -426,7 +426,9 @@ ConfirmSendToken.prototype.onSubmit = function (event) { ConfirmSendToken.prototype.cancel = function (event, txMeta) { event.preventDefault() - this.props.cancelTransaction(txMeta) + const { cancelTransaction } = this.props + + cancelTransaction(txMeta) .then(() => this.props.history.push(DEFAULT_ROUTE)) } @@ -451,39 +453,6 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) - if (props.send.editingTransactionId) { - const { - send: { - memo, - amount, - gasLimit: gas, - gasPrice, - to, - }, - } = props - - const { txParams: { from, to: tokenAddress } } = txData - - const tokenParams = { - from: ethUtil.addHexPrefix(from), - value: '0', - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - } - - const data = '0xa9059cbb' + Array.prototype.map.call( - ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - txData.txParams = { - ...tokenParams, - to: ethUtil.addHexPrefix(tokenAddress), - memo: memo && ethUtil.addHexPrefix(memo), - data, - } - } - // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js index 806c33f0a..0c4c3f7a9 100644 --- a/ui/app/components/send/gas-fee-display-v2.js +++ b/ui/app/components/send/gas-fee-display-v2.js @@ -32,8 +32,9 @@ GasFeeDisplay.prototype.render = function () { }) : h('div.currency-display', 'Loading...'), - h('div.send-v2__sliders-icon-container', { + h('button.send-v2__sliders-icon-container', { onClick, + disabled: !gasTotal, }, [ h('i.fa.fa-sliders.send-v2__sliders-icon'), ]), diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js index 9c240972f..b3ee0899a 100644 --- a/ui/app/components/send/send-constants.js +++ b/ui/app/components/send/send-constants.js @@ -20,6 +20,8 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, { multiplierBase: 16, }) +const TOKEN_TRANSFER_FUNCTION_SIGNATURE = '0xa9059cbb' + module.exports = { MIN_GAS_PRICE_GWEI, MIN_GAS_PRICE_HEX, @@ -27,4 +29,5 @@ module.exports = { MIN_GAS_LIMIT_HEX, MIN_GAS_LIMIT_DEC, MIN_GAS_TOTAL, + TOKEN_TRANSFER_FUNCTION_SIGNATURE, } diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index 5998bd252..1ebbe597c 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -55,6 +55,8 @@ function mapStateToProps (state) { data, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, tokenContract: getSelectedTokenContract(state), + unapprovedTxs: state.metamask.unapprovedTxs, + network: state.metamask.network, } } @@ -69,6 +71,7 @@ function mapDispatchToProps (dispatch) { ), signTx: txParams => dispatch(actions.signTx(txParams)), updateAndApproveTx: txParams => dispatch(actions.updateAndApproveTx(txParams)), + updateTx: txData => dispatch(actions.updateTransaction(txData)), setSelectedAddress: address => dispatch(actions.setSelectedAddress(address)), addToAddressBook: address => dispatch(actions.addToAddressBook(address)), updateGasTotal: newTotal => dispatch(actions.updateGasTotal(newTotal)), @@ -82,7 +85,6 @@ function mapDispatchToProps (dispatch) { updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)), goHome: () => dispatch(actions.goHome()), clearSend: () => dispatch(actions.clearSend()), - backToConfirmScreen: editingTransactionId => dispatch(actions.showConfTxPage({ id: editingTransactionId })), setMaxModeTo: bool => dispatch(actions.setMaxModeTo(bool)), } } diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index c5993e3d3..2270b8236 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -1,308 +1,242 @@ -const PersistentForm = require('../../lib/persistent-form') const h = require('react-hyperscript') const inherits = require('util').inherits +const Component = require('react').Component const connect = require('react-redux').connect -const actions = require('../actions') -const Qr = require('./qr-code') -const isValidAddress = require('../util').isValidAddress -module.exports = connect(mapStateToProps)(ShapeshiftForm) +const classnames = require('classnames') +const { qrcode } = require('qrcode-npm') +const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions') +const { isValidAddress } = require('../util') +const SimpleDropdown = require('./dropdowns/simple-dropdown') function mapStateToProps (state) { + const { + coinOptions, + tokenExchangeRates, + selectedAddress, + } = state.metamask + return { - warning: state.appState.warning, - isSubLoading: state.appState.isSubLoading, - qrRequested: state.appState.qrRequested, + coinOptions, + tokenExchangeRates, + selectedAddress, } } -inherits(ShapeshiftForm, PersistentForm) +function mapDispatchToProps (dispatch) { + return { + shapeShiftSubview: () => dispatch(shapeShiftSubview()), + pairUpdate: coin => dispatch(pairUpdate(coin)), + buyWithShapeShift: data => dispatch(buyWithShapeShift(data)), + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(ShapeshiftForm) +inherits(ShapeshiftForm, Component) function ShapeshiftForm () { - PersistentForm.call(this) - this.persistentFormParentId = 'shapeshift-buy-form' + Component.call(this) + + this.state = { + depositCoin: 'btc', + refundAddress: '', + showQrCode: false, + depositAddress: '', + errorMessage: '', + isLoading: false, + bought: false, + } } -ShapeshiftForm.prototype.render = function () { - return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain() +ShapeshiftForm.prototype.componentWillMount = function () { + this.props.shapeShiftSubview() } -ShapeshiftForm.prototype.renderMain = function () { - const marketinfo = this.props.buyView.formView.marketinfo - const coinOptions = this.props.buyView.formView.coinOptions - var coin = marketinfo.pair.split('_')[0].toUpperCase() - - return h('.flex-column', { - style: { - position: 'relative', - padding: '25px', - paddingTop: '5px', - width: '90%', - minHeight: '215px', - alignItems: 'center', - overflowY: 'auto', - }, - }, [ - h('.flex-row', { - style: { - justifyContent: 'center', - alignItems: 'baseline', - height: '42px', - }, - }, [ - h('img', { - src: coinOptions[coin].image, - width: '25px', - height: '25px', - style: { - marginRight: '5px', - }, - }), - - h('.input-container', { - position: 'relative', - }, [ - h('input#fromCoin.buy-inputs.ex-coins', { - type: 'text', - list: 'coinList', - autoFocus: true, - dataset: { - persistentFormId: 'input-coin', - }, - style: { - boxSizing: 'border-box', - }, - onChange: this.handleLiveInput.bind(this), - defaultValue: 'BTC', - }), +ShapeshiftForm.prototype.onCoinChange = function (e) { + const coin = e.target.value + this.setState({ + depositCoin: coin, + errorMessage: '', + }) + this.props.pairUpdate(coin) +} - this.renderCoinList(), +ShapeshiftForm.prototype.onBuyWithShapeShift = function () { + this.setState({ + isLoading: true, + showQrCode: true, + }) - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'absolute', - }, - }), - ]), + const { + buyWithShapeShift, + selectedAddress: withdrawal, + } = this.props + const { + refundAddress: returnAddress, + depositCoin, + } = this.state + const pair = `${depositCoin}_eth` + const data = { + withdrawal, + pair, + returnAddress, + // Public api key + 'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6', + } - h('.icon-control', { - style: { - position: 'relative', - }, - }, [ - // Not visible on the screen, can't see it on master. - - // h('i.fa.fa-refresh.fa-4.orange', { - // style: { - // bottom: '5px', - // left: '5px', - // color: '#F7861C', - // }, - // onClick: this.updateCoin.bind(this), - // }), - h('i.fa.fa-chevron-right.fa-4.orange', { - style: { - position: 'absolute', - bottom: '35%', - left: '0%', - color: '#F7861C', - }, - onClick: this.updateCoin.bind(this), - }), - ]), + if (isValidAddress(withdrawal)) { + buyWithShapeShift(data) + .then(d => this.setState({ + showQrCode: true, + depositAddress: d.deposit, + isLoading: false, + })) + .catch(() => this.setState({ + showQrCode: false, + errorMessage: 'Invalid Request', + isLoading: false, + })) + } +} - h('#toCoin.ex-coins', marketinfo.pair.split('_')[1].toUpperCase()), +ShapeshiftForm.prototype.renderMetadata = function (label, value) { + return h('div', {className: 'shapeshift-form__metadata-wrapper'}, [ - h('img', { - src: coinOptions[marketinfo.pair.split('_')[1].toUpperCase()].image, - width: '25px', - height: '25px', - style: { - marginLeft: '5px', - }, - }), + h('div.shapeshift-form__metadata-label', {}, [ + h('span', `${label}:`), ]), - h('.flex-column', { - style: { - marginTop: '1%', - alignItems: 'flex-start', - }, - }, [ - this.props.warning ? - this.props.warning && - h('span.error.flex-center', { - style: { - textAlign: 'center', - width: '229px', - height: '82px', - }, - }, this.props.warning) - : this.renderInfo(), - - this.renderRefundAddressForCoin(coin), + h('div.shapeshift-form__metadata-value', {}, [ + h('span', value), ]), ]) } -ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) { - return h(this.activeToggle('.input-container'), { - style: { - marginTop: '1%', - }, - }, [ - - h('div', `${coin} Address:`), - - h('input#fromCoinAddress.buy-inputs', { - type: 'text', - placeholder: `Your ${coin} Refund Address`, - dataset: { - persistentFormId: 'refund-address', - - }, - style: { - boxSizing: 'border-box', - width: '227px', - height: '30px', - padding: ' 5px ', - }, - }), - - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'absolute', - }, - }), - h('div.flex-row', { - style: { - justifyContent: 'flex-start', - }, - }, [ - h('button', { - onClick: this.shift.bind(this), - style: { - marginTop: '1%', - }, - }, - 'Submit'), - ]), +ShapeshiftForm.prototype.renderMarketInfo = function () { + const { depositCoin } = this.state + const coinPair = `${depositCoin}_eth` + const { tokenExchangeRates } = this.props + const { + limit, + rate, + minimum, + } = tokenExchangeRates[coinPair] || {} + + return h('div.shapeshift-form__metadata', {}, [ + + this.renderMetadata('Status', limit ? 'Available' : 'Unavailable'), + this.renderMetadata('Limit', limit), + this.renderMetadata('Exchange Rate', rate), + this.renderMetadata('Minimum', minimum), + ]) } -ShapeshiftForm.prototype.shift = function () { - var props = this.props - var withdrawal = this.props.buyView.buyAddress - var returnAddress = document.getElementById('fromCoinAddress').value - var pair = this.props.buyView.formView.marketinfo.pair - var data = { - 'withdrawal': withdrawal, - 'pair': pair, - 'returnAddress': returnAddress, - // Public api key - 'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6', - } - var message = [ - `Deposit Limit: ${props.buyView.formView.marketinfo.limit}`, - `Deposit Minimum:${props.buyView.formView.marketinfo.minimum}`, - ] - if (isValidAddress(withdrawal)) { - this.props.dispatch(actions.coinShiftRquest(data, message)) - } -} +ShapeshiftForm.prototype.renderQrCode = function () { + const { depositAddress, isLoading } = this.state + const qrImage = qrcode(4, 'M') + qrImage.addData(depositAddress) + qrImage.make() -ShapeshiftForm.prototype.renderCoinList = function () { - var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => { - return h('option', { - value: item, - }, item) - }) + return h('div.shapeshift-form', {}, [ - return h('datalist#coinList', { - onClick: (event) => { - event.preventDefault() - }, - }, list) -} + h('div.shapeshift-form__deposit-instruction', [ + 'Deposit your BTC to the address below:', + ]), -ShapeshiftForm.prototype.updateCoin = function (event) { - event.preventDefault() - const props = this.props - var coinOptions = this.props.buyView.formView.coinOptions - var coin = document.getElementById('fromCoin').value - - if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { - var message = 'Not a valid coin' - return props.dispatch(actions.displayWarning(message)) - } else { - return props.dispatch(actions.pairUpdate(coin)) - } -} + h('div', depositAddress), -ShapeshiftForm.prototype.handleLiveInput = function () { - const props = this.props - var coinOptions = this.props.buyView.formView.coinOptions - var coin = document.getElementById('fromCoin').value + h('div.shapeshift-form__qr-code', [ + isLoading + ? h('img', { + src: 'images/loading.svg', + style: { width: '60px'}, + }) + : h('div', { + dangerouslySetInnerHTML: { __html: qrImage.createTableTag(4) }, + }), + ]), - if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { - return null - } else { - return props.dispatch(actions.pairUpdate(coin)) - } -} + this.renderMarketInfo(), -ShapeshiftForm.prototype.renderInfo = function () { - const marketinfo = this.props.buyView.formView.marketinfo - const coinOptions = this.props.buyView.formView.coinOptions - var coin = marketinfo.pair.split('_')[0].toUpperCase() - - return h('span', { - style: { - }, - }, [ - h('h3.flex-row.text-transform-uppercase', { - style: { - color: '#868686', - paddingTop: '4px', - justifyContent: 'space-around', - textAlign: 'center', - fontSize: '17px', - }, - }, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`), - h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]), - h('.marketinfo', ['Exchange Rate: ', `${marketinfo.rate}`]), - h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]), - h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]), ]) } -ShapeshiftForm.prototype.activeToggle = function (elementType) { - if (!this.props.buyView.formView.response || this.props.warning) return elementType - return `${elementType}.inactive` -} -ShapeshiftForm.prototype.renderLoading = function () { - return h('span', { - style: { - position: 'absolute', - left: '70px', - bottom: '194px', - background: 'transparent', - width: '229px', - height: '82px', - display: 'flex', - justifyContent: 'center', - }, - }, [ - h('img', { - style: { - width: '60px', - }, - src: 'images/loading.svg', - }), - ]) +ShapeshiftForm.prototype.render = function () { + const { coinOptions, btnClass } = this.props + const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state + const coinPair = `${depositCoin}_eth` + const { tokenExchangeRates } = this.props + const token = tokenExchangeRates[coinPair] + + return h('div.shapeshift-form-wrapper', [ + showQrCode + ? this.renderQrCode() + : h('div.shapeshift-form', [ + h('div.shapeshift-form__selectors', [ + + h('div.shapeshift-form__selector', [ + + h('div.shapeshift-form__selector-label', 'Deposit'), + + h(SimpleDropdown, { + selectedOption: this.state.depositCoin, + onSelect: this.onCoinChange, + options: Object.entries(coinOptions).map(([coin]) => ({ + value: coin.toLowerCase(), + displayValue: coin, + })), + }), + + ]), + + h('div.icon.shapeshift-form__caret', { + style: { backgroundImage: 'url(images/caret-right.svg)'}, + }), + + h('div.shapeshift-form__selector', [ + + h('div.shapeshift-form__selector-label', [ + 'Receive', + ]), + + h('div.shapeshift-form__selector-input', ['ETH']), + + ]), + + ]), + + h('div', { + className: classnames('shapeshift-form__address-input-wrapper', { + 'shapeshift-form__address-input-wrapper--error': errorMessage, + }), + }, [ + + h('div.shapeshift-form__address-input-label', [ + 'Your Refund Address', + ]), + + h('input.shapeshift-form__address-input', { + type: 'text', + onChange: e => this.setState({ + refundAddress: e.target.value, + errorMessage: '', + }), + }), + + h('divshapeshift-form__address-input-error-message', [errorMessage]), + ]), + + this.renderMarketInfo(), + + ]), + + !depositAddress && h('button.shapeshift-form__shapeshift-buy-btn', { + className: btnClass, + disabled: !token, + onClick: () => this.onBuyWithShapeShift(), + }, ['Buy']), + + ]) } diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 43973de63..111a77df4 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -16,6 +16,7 @@ module.exports = connect(mapStateToProps)(ShiftListItem) function mapStateToProps (state) { return { + selectedAddress: state.metamask.selectedAddress, conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, } @@ -28,36 +29,39 @@ function ShiftListItem () { } ShiftListItem.prototype.render = function () { + const { selectedAddress, receivingAddress } = this.props return ( - h('div.tx-list-item.tx-list-clickable', { - style: { - paddingTop: '20px', - paddingBottom: '20px', - justifyContent: 'space-around', - alignItems: 'center', - }, - }, [ - h('div', { + selectedAddress === receivingAddress + ? h('div.tx-list-item.tx-list-clickable', { style: { - width: '0px', - position: 'relative', - bottom: '19px', + paddingTop: '20px', + paddingBottom: '20px', + justifyContent: 'space-around', + alignItems: 'center', }, }, [ - h('img', { - src: 'https://info.shapeshift.io/sites/default/files/logo.png', + h('div', { style: { - height: '35px', - width: '132px', - position: 'absolute', - clip: 'rect(0px,23px,34px,0px)', + width: '0px', + position: 'relative', + bottom: '19px', }, - }), - ]), + }, [ + h('img', { + src: 'https://info.shapeshift.io/sites/default/files/logo.png', + style: { + height: '35px', + width: '132px', + position: 'absolute', + clip: 'rect(0px,23px,34px,0px)', + }, + }), + ]), - this.renderInfo(), - this.renderUtilComponents(), - ]) + this.renderInfo(), + this.renderUtilComponents(), + ]) + : null ) } diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js index b40c0ec0d..59552f4a0 100644 --- a/ui/app/components/token-cell.js +++ b/ui/app/components/token-cell.js @@ -86,7 +86,9 @@ TokenCell.prototype.render = function () { numberOfDecimals: 2, conversionRate: currentTokenToFiatRate, }) - formattedFiat = `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` + formattedFiat = currentTokenInFiat.toString() === '0' + ? '' + : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` } const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol @@ -104,7 +106,7 @@ TokenCell.prototype.render = function () { h(Identicon, { className: 'token-list-item__identicon', - diameter: 45, + diameter: 50, address, network, }), diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 255f0e5eb..4e3d2cb93 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -1,6 +1,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits +const connect = require('react-redux').connect const EthBalance = require('./eth-balance') const addressSummary = require('../util').addressSummary @@ -9,18 +10,33 @@ const CopyButton = require('./copyButton') const vreme = new (require('vreme'))() const Tooltip = require('./tooltip') const numberToBN = require('number-to-bn') +const actions = require('../actions') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') -module.exports = TransactionListItem + +const mapDispatchToProps = dispatch => { + return { + retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), + } +} + +module.exports = connect(null, mapDispatchToProps)(TransactionListItem) inherits(TransactionListItem, Component) function TransactionListItem () { Component.call(this) } +TransactionListItem.prototype.showRetryButton = function () { + const { transaction = {} } = this.props + const { status, time } = transaction + return status === 'submitted' && Date.now() - time > 30000 +} + TransactionListItem.prototype.render = function () { const { transaction, network, conversionRate, currentCurrency } = this.props + const { status } = transaction if (transaction.key === 'shapeshift') { if (network === '1') return h(ShiftListItem, transaction) } @@ -32,7 +48,7 @@ TransactionListItem.prototype.render = function () { var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) - var isPending = transaction.status === 'unapproved' + var isPending = status === 'unapproved' let txParams if (isTx) { txParams = transaction.txParams @@ -44,7 +60,7 @@ TransactionListItem.prototype.render = function () { const isClickable = ('hash' in transaction && isLinkable) || isPending return ( - h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { + h('.transaction-list-item.flex-column', { onClick: (event) => { if (isPending) { this.props.showTx(transaction.id) @@ -56,51 +72,92 @@ TransactionListItem.prototype.render = function () { }, style: { padding: '20px 0', + alignItems: 'center', }, }, [ - - h('.identicon-wrapper.flex-column.flex-center.select-none', [ - h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + h(`.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { + style: { + width: '100%', + }, + }, [ + h('.identicon-wrapper.flex-column.flex-center.select-none', [ + h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + ]), + + h(Tooltip, { + title: 'Transaction Number', + position: 'right', + }, [ + h('span', { + style: { + display: 'flex', + cursor: 'normal', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: '10px', + }, + }, nonce), + ]), + + h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ + domainField(txParams), + h('div', date), + recipientField(txParams, transaction, isTx, isMsg), + ]), + + // Places a copy button if tx is successful, else places a placeholder empty div. + transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), + + isTx ? h(EthBalance, { + value: txParams.value, + conversionRate, + currentCurrency, + width: '55px', + shorten: true, + showFiat: false, + style: {fontSize: '15px'}, + }) : h('.flex-column'), ]), - h(Tooltip, { - title: 'Transaction Number', - position: 'right', + this.showRetryButton() && h('.transition-list-item__retry.grow-on-hover', { + onClick: event => { + event.stopPropagation() + this.resubmit() + }, + style: { + height: '22px', + borderRadius: '22px', + color: '#F9881B', + padding: '0 20px', + backgroundColor: '#FFE3C9', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + fontSize: '8px', + cursor: 'pointer', + }, }, [ - h('span', { + h('div', { style: { - display: 'flex', - cursor: 'normal', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - padding: '10px', + paddingRight: '2px', }, - }, nonce), - ]), - - h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ - domainField(txParams), - h('div', date), - recipientField(txParams, transaction, isTx, isMsg), + }, 'Taking too long?'), + h('div', { + style: { + textDecoration: 'underline', + }, + }, 'Retry with a higher gas price here'), ]), - - // Places a copy button if tx is successful, else places a placeholder empty div. - transaction.hash ? h(CopyButton, { value: transaction.hash }) : h('div', {style: { display: 'flex', alignItems: 'center', width: '26px' }}), - - isTx ? h(EthBalance, { - value: txParams.value, - conversionRate, - currentCurrency, - width: '55px', - shorten: true, - showFiat: false, - style: {fontSize: '15px'}, - }) : h('.flex-column'), ]) ) } +TransactionListItem.prototype.resubmit = function () { + const { transaction } = this.props + this.props.retryTransaction(transaction.id) +} + function domainField (txParams) { return h('div', { style: { diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index 4e76147a1..7ccc5c315 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -170,6 +170,7 @@ TxListItem.prototype.getSendTokenTotal = async function () { TxListItem.prototype.render = function () { const { transactionStatus, + transactionAmount, onClick, transActionId, dateString, @@ -177,6 +178,7 @@ TxListItem.prototype.render = function () { className, } = this.props const { total, fiatTotal } = this.state + const showFiatTotal = transactionAmount !== '0x0' && fiatTotal return h(`div${className || ''}`, { key: transActionId, @@ -232,13 +234,9 @@ TxListItem.prototype.render = function () { style: {}, }, [ - h('span', { - className: classnames('tx-list-value', { - 'tx-list-value--confirmed': transactionStatus === 'confirmed', - }), - }, total), + h('span.tx-list-value', total), - fiatTotal && h('span.tx-list-fiat-value', fiatTotal), + showFiatTotal && h('span.tx-list-fiat-value', fiatTotal), ]), ]), diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 2c4c3dd31..ca4fd81d6 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -42,23 +42,22 @@ TxList.prototype.componentWillMount = function () { } TxList.prototype.render = function () { - return h('div.flex-column.tx-list-container', {}, [ - + return h('div.flex-column', [ h('div.flex-row.tx-list-header-wrapper', [ h('div.flex-row.tx-list-header', [ h('div', 'transactions'), ]), ]), - - this.renderTransaction(), - + 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)) + ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate, i)) : [h( 'div.tx-list-item.tx-list-item--empty', { key: 'tx-list-none' }, @@ -67,12 +66,16 @@ TxList.prototype.renderTransaction = function () { } // TODO: Consider moving TxListItem into a separate component -TxList.prototype.renderTransactionListItem = function (transaction, conversionRate) { +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) + return h('div', { + key: `shapeshift${index}`, + }, [ + h(ShiftListItem, transaction), + ]) } const props = { diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 46029166e..60c108c36 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -74,18 +74,14 @@ TxView.prototype.renderButtons = function () { return !selectedToken ? ( h('div.flex-row.flex-center.hero-balance-buttons', [ - h('button.btn-clear', { - style: { - textAlign: 'center', - }, + h('button.btn-clear.hero-balance-button', { onClick: () => showModal({ - name: 'BUY', + name: 'DEPOSIT_ETHER', }), }, 'DEPOSIT'), - h('button.btn-clear', { + h('button.btn-clear.hero-balance-button', { style: { - textAlign: 'center', marginLeft: '0.8em', }, onClick: () => history.push(SEND_ROUTE), @@ -94,11 +90,7 @@ TxView.prototype.renderButtons = function () { ) : ( h('div.flex-row.flex-center.hero-balance-buttons', [ - h('button.btn-clear', { - style: { - textAlign: 'center', - marginLeft: '0.8em', - }, + h('button.btn-clear.hero-balance-button', { onClick: () => history.push(SEND_ROUTE), }, 'SEND'), ]) @@ -114,7 +106,7 @@ TxView.prototype.render = function () { h('div.flex-row.phone-visible', { style: { - margin: '1em 0.9em', + margin: '1.5em 1.2em 0', justifyContent: 'space-between', alignItems: 'center', }, @@ -150,7 +142,7 @@ TxView.prototype.render = function () { !isMascara && h('div.open-in-browser', { onClick: () => global.platform.openExtensionInBrowser(), - }, [h('img', { src: 'images/open.svg' })]), + }, [h('img', { src: 'images/popout.svg' })]), ]), diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js index 72ffc5d65..c7f81a305 100644 --- a/ui/app/components/wallet-view.js +++ b/ui/app/components/wallet-view.js @@ -136,7 +136,7 @@ WalletView.prototype.render = function () { selectedIdentity.name, ]), - h('button.wallet-view__details-button', 'DETAILS'), + h('button.btn-clear.wallet-view__details-button', 'DETAILS'), ]), ]), @@ -157,7 +157,7 @@ WalletView.prototype.render = function () { h(TokenList), - h('button.wallet-view__add-token-button', { + h('button.btn-clear.wallet-view__add-token-button', { onClick: () => history.push(ADD_TOKEN_ROUTE), }, 'Add Token'), ]) |