diff options
Diffstat (limited to 'ui/app/components')
26 files changed, 614 insertions, 365 deletions
diff --git a/ui/app/components/account-eth-balance.js b/ui/app/components/account-eth-balance.js deleted file mode 100644 index 8d693685f..000000000 --- a/ui/app/components/account-eth-balance.js +++ /dev/null @@ -1,140 +0,0 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits -const connect = require('react-redux').connect -const formatBalance = require('../util').formatBalance -const generateBalanceObject = require('../util').generateBalanceObject -const Tooltip = require('./tooltip.js') - -module.exports = connect(mapStateToProps)(EthBalanceComponent) - -function mapStateToProps (state) { - return { - conversionRate: state.metamask.conversionRate, - conversionDate: state.metamask.conversionDate, - currentFiat: state.metamask.currentFiat, - } -} - -inherits(EthBalanceComponent, Component) -function EthBalanceComponent () { - Component.call(this) -} - -EthBalanceComponent.prototype.render = function () { - var state = this.props - var style = state.style - - const value = formatBalance(state.value, 6) - var width = state.width - - return ( - - h('.ether-balance', { - style: style, - }, [ - h('.ether-balance-amount', { - style: { - display: 'inline', - width: width, - }, - }, this.renderBalance(value, state)), - ]) - - ) -} -EthBalanceComponent.prototype.renderBalance = function (value, state) { - if (value === 'None') return value - var balanceObj = generateBalanceObject(value, state.shorten ? 1 : 3) - var balance, fiatDisplayNumber, fiatTooltipNumber - var splitBalance = value.split(' ') - var ethNumber = splitBalance[0] - var ethSuffix = splitBalance[1] - - - if (state.conversionRate !== 0) { - fiatTooltipNumber = Number(splitBalance[0]) * state.conversionRate - fiatDisplayNumber = fiatTooltipNumber.toFixed(2) - } else { - fiatDisplayNumber = 'N/A' - } - - var fiatSuffix = state.currentFiat - - if (state.shorten) { - balance = balanceObj.shortBalance - } else { - balance = balanceObj.balance - } - - var label = balanceObj.label - - return ( - h('.flex-column', [ - h(Tooltip, { - position: 'bottom', - title: `${ethNumber} ${ethSuffix}`, - }, [ - h('.flex-row', { - style: { - alignItems: 'flex-end', - lineHeight: '13px', - fontFamily: 'Montserrat Light', - textRendering: 'geometricPrecision', - marginBottom: '5px', - }, - }, [ - h('div', { - style: { - width: '100%', - textAlign: 'right', - }, - }, balance), - h('div', { - style: { - color: '#AEAEAE', - marginLeft: '5px', - }, - }, label), - ]), - ]), - h(Tooltip, { - position: 'bottom', - title: `${fiatTooltipNumber} ${fiatSuffix}`, - }, [ - fiatDisplay(fiatDisplayNumber, fiatSuffix), - ]), - ]) - ) -} - -function fiatDisplay (fiatDisplayNumber, fiatSuffix) { - if (fiatDisplayNumber !== 'N/A') { - return h('.flex-row', { - style: { - alignItems: 'flex-end', - lineHeight: '13px', - fontFamily: 'Montserrat Light', - textRendering: 'geometricPrecision', - }, - }, [ - h('div', { - style: { - width: '100%', - textAlign: 'right', - fontSize: '12px', - color: '#333333', - }, - }, fiatDisplayNumber), - h('div', { - style: { - color: '#AEAEAE', - marginLeft: '5px', - fontSize: '12px', - }, - }, fiatSuffix), - ]) - } else { - return h('div') - } -} diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js index f36b9faeb..6d8b099a5 100644 --- a/ui/app/components/account-export.js +++ b/ui/app/components/account-export.js @@ -3,6 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const copyToClipboard = require('copy-to-clipboard') const actions = require('../actions') +const ethUtil = require('ethereumjs-util') module.exports = ExportAccountView @@ -61,7 +62,9 @@ ExportAccountView.prototype.render = function () { if (accountExported) { return h('div.privateKey', { - + style: { + margin: '0 20px', + }, }, [ h('label', 'Your private key (click to copy):'), h('p.error.cursor-pointer', { @@ -72,9 +75,9 @@ ExportAccountView.prototype.render = function () { width: '100%', }, onClick: function (event) { - copyToClipboard(accountDetail.privateKey) + copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey)) }, - }, accountDetail.privateKey), + }, ethUtil.stripHexPrefix(accountDetail.privateKey)), h('button', { onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), }, 'Done'), diff --git a/ui/app/components/account-info-link.js b/ui/app/components/account-info-link.js index 4fe3b8b5d..49c42e9ec 100644 --- a/ui/app/components/account-info-link.js +++ b/ui/app/components/account-info-link.js @@ -14,7 +14,7 @@ function AccountInfoLink () { AccountInfoLink.prototype.render = function () { const { selected, network } = this.props - const title = 'View account on etherscan' + const title = 'View account on Etherscan' const url = genAccountLink(selected, network) if (!url) { diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 742241e5b..3074bd7cd 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -6,16 +6,19 @@ const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') const extension = require('../../../app/scripts/lib/extension') +const Loading = require('./loading') +const TabBar = require('./tab-bar') module.exports = connect(mapStateToProps)(BuyButtonSubview) function mapStateToProps (state) { return { - selectedAccount: state.selectedAccount, 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, } } @@ -26,7 +29,7 @@ function BuyButtonSubview () { BuyButtonSubview.prototype.render = function () { const props = this.props - const currentForm = props.buyView.formView + const isLoading = props.isSubLoading return ( h('.buy-eth-section', [ @@ -38,7 +41,7 @@ BuyButtonSubview.prototype.render = function () { }, }, [ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { - onClick: () => props.dispatch(actions.backToAccountDetail(props.selectedAccount)), + onClick: this.backButtonContext.bind(this), style: { position: 'absolute', left: '10px', @@ -46,43 +49,56 @@ BuyButtonSubview.prototype.render = function () { }), h('h2.page-subtitle', 'Buy Eth'), ]), - h('h3.flex-row.text-transform-uppercase', { - style: { - background: '#EBEBEB', - color: '#AEAEAE', - paddingTop: '4px', - justifyContent: 'space-around', + + h(Loading, { isLoading }), + + h(TabBar, { + tabs: [ + { + content: [ + 'Coinbase', + h('a', { + onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), + }, [ + h('i.fa.fa-question-circle', { + style: { + margin: '0px 5px', + }, + }), + ]), + ], + key: 'coinbase', + }, + { + content: [ + 'Shapeshift', + h('a', { + href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', + onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), + }, [ + h('i.fa.fa-question-circle', { + style: { + margin: '0px 5px', + }, + }), + ]), + ], + key: 'shapeshift', + }, + ], + defaultTab: 'coinbase', + tabSelected: (key) => { + switch (key) { + case 'coinbase': + props.dispatch(actions.coinBaseSubview()) + break + case 'shapeshift': + props.dispatch(actions.shapeShiftSubview(props.provider.type)) + break + } }, - }, [ - h(currentForm.coinbase ? '.activeForm' : '.inactiveForm.pointer', { - onClick: () => props.dispatch(actions.coinBaseSubview()), - }, 'Coinbase'), - h('a', { - onClick: (event) => this.navigateTo('https://github.com/MetaMask/faq/blob/master/COINBASE.md'), - }, [ - h('i.fa.fa-question-circle', { - style: { - position: 'relative', - right: '33px', - }, - }), - ]), - h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm.pointer', { - onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)), - }, 'Shapeshift'), + }), - h('a', { - href: 'https://github.com/MetaMask/faq/blob/master/COINBASE.md', - onClick: (event) => this.navigateTo('https://info.shapeshift.io/about'), - }, [ - h('i.fa.fa-question-circle', { - style: { - position: 'relative', - right: '28px', - }, - }), - ]), - ]), this.formVersionSubview(), ]) ) @@ -106,9 +122,9 @@ BuyButtonSubview.prototype.formVersionSubview = function () { style: { width: '225px', }, - }, 'In order to access this feature please switch too the Main Network'), - h('h3.text-transform-uppercase', 'or:'), - this.props.network === '2' ? h('button.text-transform-uppercase', { + }, 'In order to access this feature, please switch to the Main Network'), + (this.props.network === '3') ? h('h3.text-transform-uppercase', 'or:') : null, + (this.props.network === '3') ? h('button.text-transform-uppercase', { onClick: () => this.props.dispatch(actions.buyEth()), style: { marginTop: '15px', @@ -121,3 +137,11 @@ BuyButtonSubview.prototype.formVersionSubview = function () { BuyButtonSubview.prototype.navigateTo = function (url) { extension.tabs.create({ url }) } + +BuyButtonSubview.prototype.backButtonContext = function () { + if (this.props.context === 'confTx') { + this.props.dispatch(actions.showConfTxPage(false)) + } else { + this.props.dispatch(actions.goHome()) + } +} diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index efd05ec96..40f5719bb 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -7,16 +7,15 @@ const actions = require('../actions') const isValidAddress = require('../util').isValidAddress module.exports = connect(mapStateToProps)(CoinbaseForm) -function mapStateToProps(state) { +function mapStateToProps (state) { return { - selectedAccount: state.selectedAccount, warning: state.appState.warning, } } inherits(CoinbaseForm, Component) -function CoinbaseForm() { +function CoinbaseForm () { Component.call(this) } @@ -72,7 +71,7 @@ CoinbaseForm.prototype.render = function () { lineHeight: '13px', }, }, - `there is a USD$ 5 a day max and a USD$ 50 + `there is a USD$ 15 a day max and a USD$ 50 dollar limit per the life time of an account without a coinbase account. A fee of 3.75% will be aplied to debit/credit cards.`), @@ -116,15 +115,14 @@ CoinbaseForm.prototype.toCoinbase = function () { props.dispatch(actions.buyEth(address, props.buyView.amount)) } else if (!isValidAmountforCoinBase(amount).valid) { message = isValidAmountforCoinBase(amount).message - return props.dispatch(actions.showWarning(message)) + return props.dispatch(actions.displayWarning(message)) } else { message = 'Receiving address is invalid.' - return props.dispatch(actions.showWarning(message)) + return props.dispatch(actions.displayWarning(message)) } } CoinbaseForm.prototype.renderLoading = function () { - return h('img', { style: { width: '27px', @@ -134,18 +132,17 @@ CoinbaseForm.prototype.renderLoading = function () { }) } -function isValidAmountforCoinBase(amount) { +function isValidAmountforCoinBase (amount) { amount = parseFloat(amount) - if (amount) { - if (amount <= 5 && amount > 0) { + if (amount <= 15 && amount > 0) { return { valid: true, } - } else if (amount > 5) { + } else if (amount > 15) { return { valid: false, - message: 'The amount can not be greater then $5', + message: 'The amount can not be greater then $15', } } else { return { diff --git a/ui/app/components/copyButton.js b/ui/app/components/copyButton.js index a01603585..a25d0719c 100644 --- a/ui/app/components/copyButton.js +++ b/ui/app/components/copyButton.js @@ -50,12 +50,10 @@ CopyButton.prototype.render = function () { ]) } -CopyButton.prototype.debounceRestore = function() { - +CopyButton.prototype.debounceRestore = function () { this.setState({ copied: true }) clearTimeout(this.timeout) this.timeout = setTimeout(() => { this.setState({ copied: false }) }, 850) - } diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index 0ca1988c6..9f002234e 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -32,17 +32,17 @@ DropMenuItem.prototype.render = function () { } DropMenuItem.prototype.activeNetworkRender = function () { - let activeNetwork = this.props.activeNetworkRender - let { provider } = this.props - let providerType = provider ? provider.type : null + const activeNetwork = this.props.activeNetworkRender + const { provider } = this.props + const providerType = provider ? provider.type : null if (activeNetwork === undefined) return switch (this.props.label) { case 'Main Ethereum Network': if (providerType === 'mainnet') return h('.check', '✓') break - case 'Morden Test Network': - if (activeNetwork === '2') return h('.check', '✓') + case 'Ropsten Test Network': + if (provider.type === 'testnet') return h('.check', '✓') break case 'Localhost 8545': if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js index 498873faa..57ca84564 100644 --- a/ui/app/components/eth-balance.js +++ b/ui/app/components/eth-balance.js @@ -4,6 +4,7 @@ const inherits = require('util').inherits const formatBalance = require('../util').formatBalance const generateBalanceObject = require('../util').generateBalanceObject const Tooltip = require('./tooltip.js') +const FiatValue = require('./fiat-value.js') module.exports = EthBalanceComponent @@ -13,11 +14,12 @@ function EthBalanceComponent () { } EthBalanceComponent.prototype.render = function () { - var state = this.props - var style = state.style + var props = this.props + let { value } = props + var style = props.style var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true - const value = formatBalance(state.value, 6, needsParse) - var width = state.width + value = value ? formatBalance(value, 6, needsParse) : '...' + var width = props.width return ( @@ -35,15 +37,17 @@ EthBalanceComponent.prototype.render = function () { ) } EthBalanceComponent.prototype.renderBalance = function (value) { - var state = this.props + var props = this.props if (value === 'None') return value - var balanceObj = generateBalanceObject(value, state.shorten ? 1 : 3) + if (value === '...') return value + var balanceObj = generateBalanceObject(value, props.shorten ? 1 : 3) var balance var splitBalance = value.split(' ') var ethNumber = splitBalance[0] var ethSuffix = splitBalance[1] + const showFiat = 'showFiat' in props ? props.showFiat : true - if (state.shorten) { + if (props.shorten) { balance = balanceObj.shortBalance } else { balance = balanceObj.balance @@ -55,8 +59,8 @@ EthBalanceComponent.prototype.renderBalance = function (value) { h(Tooltip, { position: 'bottom', title: `${ethNumber} ${ethSuffix}`, - }, [ - h('.flex-column', { + }, h('div.flex-column', [ + h('.flex-row', { style: { alignItems: 'flex-end', lineHeight: '13px', @@ -74,9 +78,12 @@ EthBalanceComponent.prototype.renderBalance = function (value) { style: { color: ' #AEAEAE', fontSize: '12px', + marginLeft: '5px', }, }, label), ]), - ]) + + showFiat ? h(FiatValue, { value: props.value }) : null, + ])) ) } diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js new file mode 100644 index 000000000..13ee48245 --- /dev/null +++ b/ui/app/components/fiat-value.js @@ -0,0 +1,71 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const formatBalance = require('../util').formatBalance + +module.exports = connect(mapStateToProps)(FiatValue) + +function mapStateToProps (state) { + return { + conversionRate: state.metamask.conversionRate, + currentFiat: state.metamask.currentFiat, + } +} + +inherits(FiatValue, Component) +function FiatValue () { + Component.call(this) +} + +FiatValue.prototype.render = function () { + const props = this.props + const value = formatBalance(props.value, 6) + + if (value === 'None') return value + var fiatDisplayNumber, fiatTooltipNumber + var splitBalance = value.split(' ') + + if (props.conversionRate !== 0) { + fiatTooltipNumber = Number(splitBalance[0]) * props.conversionRate + fiatDisplayNumber = fiatTooltipNumber.toFixed(2) + } else { + fiatDisplayNumber = 'N/A' + fiatTooltipNumber = 'Unknown' + } + + var fiatSuffix = props.currentFiat + + return fiatDisplay(fiatDisplayNumber, fiatSuffix) +} + +function fiatDisplay (fiatDisplayNumber, fiatSuffix) { + if (fiatDisplayNumber !== 'N/A') { + return h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: '13px', + fontFamily: 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('div', { + style: { + width: '100%', + textAlign: 'right', + fontSize: '12px', + color: '#333333', + }, + }, fiatDisplayNumber), + h('div', { + style: { + color: '#AEAEAE', + marginLeft: '5px', + fontSize: '12px', + }, + }, fiatSuffix), + ]) + } else { + return h('div') + } +} diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index 4b2bf899e..6d4871d02 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -16,8 +16,8 @@ function IdenticonComponent () { } IdenticonComponent.prototype.render = function () { - var state = this.props - var diameter = state.diameter || this.defaultDiameter + var props = this.props + var diameter = props.diameter || this.defaultDiameter return ( h('div', { key: 'identicon-' + this.props.address, @@ -33,15 +33,31 @@ IdenticonComponent.prototype.render = function () { } IdenticonComponent.prototype.componentDidMount = function () { - var state = this.props - var address = state.address + var props = this.props + var address = props.address if (!address) return var container = findDOMNode(this) - var diameter = state.diameter || this.defaultDiameter - var imageify = state.imageify === undefined ? true : state.imageify - var img = iconFactory.iconForAddress(address, diameter, imageify) + var diameter = props.diameter || this.defaultDiameter + var img = iconFactory.iconForAddress(address, diameter, false) container.appendChild(img) } +IdenticonComponent.prototype.componentDidUpdate = function () { + var props = this.props + var address = props.address + + if (!address) return + + var container = findDOMNode(this) + + var children = container.children + for (var i = 0; i < children.length; i++) { + container.removeChild(children[i]) + } + + var diameter = props.diameter || this.defaultDiameter + var img = iconFactory.iconForAddress(address, diameter, false) + container.appendChild(img) +} diff --git a/ui/app/components/loading.js b/ui/app/components/loading.js new file mode 100644 index 000000000..88dc535df --- /dev/null +++ b/ui/app/components/loading.js @@ -0,0 +1,50 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const ReactCSSTransitionGroup = require('react-addons-css-transition-group') + + +inherits(LoadingIndicator, Component) +module.exports = LoadingIndicator + +function LoadingIndicator () { + Component.call(this) +} + +LoadingIndicator.prototype.render = function () { + const { isLoading, loadingMessage } = this.props + + return ( + h(ReactCSSTransitionGroup, { + className: 'css-transition-group', + transitionName: 'loader', + transitionEnterTimeout: 150, + transitionLeaveTimeout: 150, + }, [ + + isLoading ? h('div', { + style: { + zIndex: 10, + position: 'absolute', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + width: '100%', + background: 'rgba(255, 255, 255, 0.5)', + }, + }, [ + h('img', { + src: 'images/loading.svg', + }), + + showMessageIfAny(loadingMessage), + ]) : null, + ]) + ) +} + +function showMessageIfAny (loadingMessage) { + if (!loadingMessage) return null + return h('span', loadingMessage) +} diff --git a/ui/app/components/mascot.js b/ui/app/components/mascot.js index f2b00262b..f015d0c4d 100644 --- a/ui/app/components/mascot.js +++ b/ui/app/components/mascot.js @@ -14,9 +14,8 @@ function Mascot () { pxNotRatio: true, width: 200, height: 200, - staticImage: './images/icon-512.png', }) - if (!this.logo.webGLSupport) return + this.refollowMouse = debounce(this.logo.setFollowMouse.bind(this.logo, true), 1000) this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false) } @@ -27,32 +26,25 @@ Mascot.prototype.render = function () { // and we dont get that until render this.handleAnimationEvents() - return ( - - h('#metamask-mascot-container') - - ) + return h('#metamask-mascot-container', { + style: { zIndex: 2 }, + }) } Mascot.prototype.componentDidMount = function () { var targetDivId = 'metamask-mascot-container' var container = document.getElementById(targetDivId) - if (!this.logo.webGLSupport) { - var staticLogo = this.logo.staticLogo - staticLogo.style.marginBottom = '40px' - container.appendChild(staticLogo) - } else { - container.appendChild(this.logo.canvas) - } + container.appendChild(this.logo.container) } Mascot.prototype.componentWillUnmount = function () { - if (!this.logo.webGLSupport) return - this.logo.canvas.remove() + this.animations = this.props.animationEventEmitter + this.animations.removeAllListeners() + this.logo.container.remove() + this.logo.stopAnimation() } Mascot.prototype.handleAnimationEvents = function () { - if (!this.logo.webGLSupport) return // only setup listeners once if (this.animations) return this.animations = this.props.animationEventEmitter @@ -61,7 +53,6 @@ Mascot.prototype.handleAnimationEvents = function () { } Mascot.prototype.lookAt = function (target) { - if (!this.logo.webGLSupport) return this.unfollowMouse() this.logo.lookAt(target) this.refollowMouse() diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 2f1bf639a..77805fd57 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -22,7 +22,6 @@ Network.prototype.render = function () { let iconName, hoverText if (networkNumber === 'loading') { - return h('img.network-indicator', { title: 'Attempting to connect to blockchain.', onClick: (event) => this.props.onClick(event), @@ -32,23 +31,22 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }) - } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' iconName = 'ethereum-network' - } else if (parseInt(networkNumber) === 2) { - hoverText = 'Morden Test Network' - iconName = 'morden-test-network' + } else if (providerName === 'testnet') { + hoverText = 'Ropsten Test Network' + iconName = 'ropsten-test-network' + } else if (parseInt(networkNumber) === 3) { + hoverText = 'Ropsten Test Network' + iconName = 'ropsten-test-network' } else { hoverText = 'Unknown Private Network' iconName = 'unknown-private-network' } + return ( - h('#network_component.flex-center.pointer', { - style: { - marginRight: '-27px', - marginLeft: '-3px', - }, + h('#network_component.pointer', { title: hoverText, onClick: (event) => this.props.onClick(event), }, [ @@ -61,21 +59,20 @@ Network.prototype.render = function () { style: { color: '#039396', }}, - 'Etherum Main Net'), + 'Ethereum Main Net'), ]) - case 'morden-test-network': + case 'ropsten-test-network': return h('.network-indicator', [ h('.menu-icon.red-dot'), h('.network-name', { style: { color: '#ff6666', }}, - 'Morden Test Net'), + 'Ropsten Test Net'), ]) default: return h('.network-indicator', [ h('i.fa.fa-question-circle.fa-lg', { - ariaHidden: true, style: { margin: '10px', color: 'rgb(125, 128, 130)', diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js new file mode 100644 index 000000000..00db734d7 --- /dev/null +++ b/ui/app/components/notice.js @@ -0,0 +1,110 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const ReactMarkdown = require('react-markdown') +const linker = require('extension-link-enabler') +const findDOMNode = require('react-dom').findDOMNode + +module.exports = Notice + +inherits(Notice, Component) +function Notice () { + Component.call(this) +} + +Notice.prototype.render = function () { + const { notice, onConfirm } = this.props + const { title, date, body } = notice + + return ( + h('.flex-column.flex-center.flex-grow', [ + h('h3.flex-center.text-transform-uppercacse.terms-header', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + width: '100%', + fontSize: '20px', + textAlign: 'center', + padding: 6, + }, + }, [ + title, + ]), + + h('h5.flex-center.text-transform-uppercacse.terms-header', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + marginBottom: 24, + width: '100%', + fontSize: '20px', + textAlign: 'center', + padding: 6, + }, + }, [ + date, + ]), + + h('style', ` + + .markdown { + overflow-x: hidden; + } + + .markdown h1, .markdown h2, .markdown h3 { + margin: 10px 0; + font-weight: bold; + } + + .markdown strong { + font-weight: bold; + } + .markdown em { + font-style: italic; + } + + .markdown p { + margin: 10px 0; + } + + .markdown a { + color: #df6b0e; + } + + `), + + h('div.markdown', { + style: { + background: 'rgb(235, 235, 235)', + height: '310px', + padding: '6px', + width: '90%', + overflowY: 'scroll', + scroll: 'auto', + }, + }, [ + h(ReactMarkdown, { + source: body, + skipHtml: true, + }), + ]), + + h('button', { + onClick: onConfirm, + style: { + marginTop: '18px', + }, + }, 'Continue'), + ]) + ) +} + +Notice.prototype.componentDidMount = function () { + var node = findDOMNode(this) + linker.setupListener(node) +} + +Notice.prototype.componentWillUnmount = function () { + var node = findDOMNode(this) + linker.teardownListener(node) +} diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js index f4bde91dc..b2cac164a 100644 --- a/ui/app/components/pending-msg.js +++ b/ui/app/components/pending-msg.js @@ -28,6 +28,15 @@ PendingMsg.prototype.render = function () { }, }, 'Sign Message'), + h('.error', { + style: { + margin: '10px', + }, + }, `Signing this message can have + dangerous side effects. Only sign messages from + sites you fully trust with your entire account. + This will be fixed in a future version.`), + // message details h(PendingTxDetails, state), diff --git a/ui/app/components/pending-tx-details.js b/ui/app/components/pending-tx-details.js index 2fb0eae3f..e8615404e 100644 --- a/ui/app/components/pending-tx-details.js +++ b/ui/app/components/pending-tx-details.js @@ -1,16 +1,12 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits -const carratInline = require('fs').readFileSync('./images/forward-carrat.svg', 'utf8') const MiniAccountPanel = require('./mini-account-panel') -const EtherBalance = require('./eth-balance') -const addressSummary = require('../util').addressSummary -const formatBalance = require('../util').formatBalance +const EthBalance = require('./eth-balance') +const util = require('../util') +const addressSummary = util.addressSummary const nameForAddress = require('../../lib/contract-namer') -const ethUtil = require('ethereumjs-util') -const BN = ethUtil.BN - module.exports = PendingTxDetails @@ -31,13 +27,9 @@ PTXP.render = function () { var account = props.accounts[address] var balance = account ? account.balance : '0x0' - var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16) - var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16) - var txFee = gasCost.mul(gasPrice) - var txValue = new BN(ethUtil.stripHexPrefix(txParams.value || '0x0'), 16) - var maxCost = txValue.add(txFee) + var txFee = txData.txFee || '' + var maxCost = txData.maxCost || '' var dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0 - var imageify = props.imageifyIdenticons === undefined ? true : props.imageifyIdenticons return ( @@ -70,7 +62,7 @@ PTXP.render = function () { fontFamily: 'Montserrat Light, Montserrat, sans-serif', }, }, [ - h(EtherBalance, { + h(EthBalance, { value: balance, inline: true, labelColor: '#F7861C', @@ -79,7 +71,7 @@ PTXP.render = function () { ]), - forwardCarrat(imageify), + forwardCarrat(), this.miniAccountPanelForRecipient(), ]), @@ -107,12 +99,12 @@ PTXP.render = function () { h('.row', [ h('.cell.label', 'Amount'), - h('.cell.value', formatBalance(txParams.value)), + h(EthBalance, { value: txParams.value }), ]), h('.cell.row', [ h('.cell.label', 'Max Transaction Fee'), - h('.cell.value', formatBalance(txFee.toString(16))), + h(EthBalance, { value: txFee.toString(16) }), ]), h('.cell.row', { @@ -129,7 +121,7 @@ PTXP.render = function () { alignItems: 'center', }, }, [ - h(EtherBalance, { + h(EthBalance, { value: maxCost.toString(16), inline: true, labelColor: 'black', @@ -154,8 +146,6 @@ PTXP.render = function () { ]), ]), // End of Table - this.warnIfNeeded(), - ]) ) } @@ -201,53 +191,16 @@ PTXP.miniAccountPanelForRecipient = function () { } } -// Should analyze if there is a DELEGATECALL opcode -// in the recipient contract, and show a warning if so. -PTXP.warnIfNeeded = function () { - const containsDelegateCall = !!this.props.txData.containsDelegateCall - - if (!containsDelegateCall) { - return null - } - - return h('span.error', { - style: { - fontFamily: 'Montserrat Light', - fontSize: '13px', - display: 'flex', - justifyContent: 'center', - }, - }, [ - h('i.fa.fa-lg.fa-info-circle', { style: { margin: '5px' } }), - h('span', ' Your identity may be used in other contracts!'), - ]) -} - - -function forwardCarrat (imageify) { - if (imageify) { - return ( - - h('img', { - src: 'images/forward-carrat.svg', - style: { - padding: '5px 6px 0px 10px', - height: '37px', - }, - }) +function forwardCarrat () { + return ( - ) - } else { - return ( + h('img', { + src: 'images/forward-carrat.svg', + style: { + padding: '5px 6px 0px 10px', + height: '37px', + }, + }) - h('div', { - dangerouslySetInnerHTML: { __html: carratInline }, - style: { - padding: '0px 6px 0px 10px', - height: '45px', - }, - }) - - ) - } + ) } diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js index 1feedbbbc..96f968929 100644 --- a/ui/app/components/pending-tx.js +++ b/ui/app/components/pending-tx.js @@ -3,7 +3,6 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const PendingTxDetails = require('./pending-tx-details') - module.exports = PendingTx inherits(PendingTx, Component) @@ -31,6 +30,24 @@ PendingTx.prototype.render = function () { } `), + txData.simulationFails ? + h('.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Transaction Error. Exception thrown in contract code.') + : null, + + state.insufficientBalance ? + h('span.error', { + style: { + marginLeft: 50, + fontSize: '0.9em', + }, + }, 'Insufficient balance for transaction') + : null, + // send + cancel h('.flex-row.flex-space-around.conf-buttons', { style: { @@ -39,17 +56,22 @@ PendingTx.prototype.render = function () { margin: '14px 25px', }, }, [ + + state.insufficientBalance ? + h('button.btn-green', { + onClick: state.buyEth, + }, 'Buy Ether') + : null, + h('button.confirm', { + disabled: state.insufficientBalance, onClick: state.sendTransaction, - style: { background: 'rgb(251,117,1)' }, }, 'Accept'), - h('button.cancel', { + h('button.cancel.btn-red', { onClick: state.cancelTransaction, - style: { background: 'rgb(254,35,17)' }, }, 'Reject'), ]), ]) ) } - diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js index c26b02b94..5488599eb 100644 --- a/ui/app/components/qr-code.js +++ b/ui/app/components/qr-code.js @@ -1,5 +1,6 @@ const Component = require('react').Component const h = require('react-hyperscript') +const qrCode = require('qrcode-npm').qrcode const inherits = require('util').inherits const connect = require('react-redux').connect const CopyButton = require('./copyButton') @@ -23,15 +24,22 @@ function QrCodeView () { QrCodeView.prototype.render = function () { var props = this.props var Qr = props.Qr + var qrImage = qrCode(4, 'M') + + qrImage.addData(Qr.data) + qrImage.make() + return h('.main-container.flex-column', { key: 'qr', style: { justifyContent: 'center', - padding: '45px', + paddingBottom: '45px', + paddingLeft: '45px', + paddingRight: '45px', alignItems: 'center', }, }, [ - Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('h3', Qr.message), + Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message), this.props.warning ? this.props.warning && h('span.error.flex-center', { style: { @@ -48,7 +56,7 @@ QrCodeView.prototype.render = function () { marginBottom: '15px', }, dangerouslySetInnerHTML: { - __html: Qr.image, + __html: qrImage.createTableTag(4), }, }), h('.flex-row', [ diff --git a/ui/app/components/range-slider.js b/ui/app/components/range-slider.js new file mode 100644 index 000000000..823f5eb01 --- /dev/null +++ b/ui/app/components/range-slider.js @@ -0,0 +1,58 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = RangeSlider + +inherits(RangeSlider, Component) +function RangeSlider () { + Component.call(this) +} + +RangeSlider.prototype.render = function () { + const state = this.state || {} + const props = this.props + const onInput = props.onInput || function () {} + const name = props.name + const { + min = 0, + max = 100, + increment = 1, + defaultValue = 50, + mirrorInput = false, + } = this.props.options + const {container, input, range} = props.style + + return ( + h('.flex-row', { + style: container, + }, [ + h('input', { + type: 'range', + name: name, + min: min, + max: max, + step: increment, + style: range, + value: state.value || defaultValue, + onChange: mirrorInput ? this.mirrorInputs.bind(this, event) : onInput, + }), + + // Mirrored input for range + mirrorInput ? h('input.large-input', { + type: 'number', + name: `${name}Mirror`, + min: min, + max: max, + value: state.value || defaultValue, + step: increment, + style: input, + onChange: this.mirrorInputs.bind(this, event), + }) : null, + ]) + ) +} + +RangeSlider.prototype.mirrorInputs = function (event) { + this.setState({value: event.target.value}) +} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index b8650f7d5..8c9686035 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -1,4 +1,4 @@ -const Component = require('react').Component +const PersistentForm = require('../../lib/persistent-form') const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect @@ -8,20 +8,21 @@ const Qr = require('./qr-code') const isValidAddress = require('../util').isValidAddress module.exports = connect(mapStateToProps)(ShapeshiftForm) -function mapStateToProps(state) { +function mapStateToProps (state) { return { - selectedAccount: state.selectedAccount, warning: state.appState.warning, isSubLoading: state.appState.isSubLoading, qrRequested: state.appState.qrRequested, } } -inherits(ShapeshiftForm, Component) +inherits(ShapeshiftForm, PersistentForm) function ShapeshiftForm () { - Component.call(this) + PersistentForm.call(this) + this.persistentFormParentId = 'shapeshift-buy-form' } + ShapeshiftForm.prototype.render = function () { return h(ReactCSSTransitionGroup, { className: 'css-transition-group', @@ -31,7 +32,6 @@ ShapeshiftForm.prototype.render = function () { }, [ this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(), ]) - } ShapeshiftForm.prototype.renderMain = function () { @@ -66,6 +66,9 @@ ShapeshiftForm.prototype.renderMain = function () { h('input#fromCoin.buy-inputs.ex-coins', { type: 'text', list: 'coinList', + dataset: { + persistentFormId: 'input-coin', + }, style: { boxSizing: 'border-box', }, @@ -122,7 +125,6 @@ ShapeshiftForm.prototype.renderMain = function () { this.props.isSubLoading ? this.renderLoading() : null, h('.flex-column', { style: { - width: '235px', alignItems: 'flex-start', }, }, [ @@ -159,6 +161,9 @@ ShapeshiftForm.prototype.renderMain = function () { h('input#fromCoinAddress.buy-inputs', { type: 'text', placeholder: `Your ${coin} Refund Address`, + dataset: { + persistentFormId: 'refund-address', + }, style: { boxSizing: 'border-box', width: '278px', @@ -236,7 +241,7 @@ ShapeshiftForm.prototype.updateCoin = function (event) { if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { var message = 'Not a valid coin' - return props.dispatch(actions.showWarning(message)) + return props.dispatch(actions.displayWarning(message)) } else { return props.dispatch(actions.pairUpdate(coin)) } @@ -261,17 +266,17 @@ ShapeshiftForm.prototype.renderInfo = function () { return h('span', { style: { - marginTop: '15px', + marginTop: '10px', marginBottom: '15px', }, }, [ h('h3.flex-row.text-transform-uppercase', { style: { - color: '#AEAEAE', + color: '#868686', paddingTop: '4px', justifyContent: 'space-around', textAlign: 'center', - fontSize: '14px', + fontSize: '17px', }, }, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`), h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]), diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 38c19eb28..e0243e247 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -26,7 +26,6 @@ function ShiftListItem () { } ShiftListItem.prototype.render = function () { - return ( h('.transaction-list-item.flex-row', { style: { diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js new file mode 100644 index 000000000..65078e0a4 --- /dev/null +++ b/ui/app/components/tab-bar.js @@ -0,0 +1,35 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits + +module.exports = TabBar + +inherits(TabBar, Component) +function TabBar () { + Component.call(this) +} + +TabBar.prototype.render = function () { + const props = this.props + const state = this.state || {} + const { tabs = [], defaultTab, tabSelected } = props + const { subview = defaultTab } = state + + return ( + h('.flex-row.space-around.text-transform-uppercase', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + paddingTop: '4px', + }, + }, tabs.map((tab) => { + const { key, content } = tab + return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', { + onClick: () => { + this.setState({ subview: key }) + tabSelected(key) + }, + }, content) + })) + ) +} diff --git a/ui/app/components/tooltip.js b/ui/app/components/tooltip.js index fb67c717e..edbc074bb 100644 --- a/ui/app/components/tooltip.js +++ b/ui/app/components/tooltip.js @@ -12,11 +12,11 @@ function Tooltip () { Tooltip.prototype.render = function () { const props = this.props + const { position, title, children } = props return h(ReactTooltip, { - position: props.position ? props.position : 'left', - title: props.title, + position: position || 'left', + title, fixed: false, - }, props.children) - + }, children) } diff --git a/ui/app/components/transaction-list-item-icon.js b/ui/app/components/transaction-list-item-icon.js index 8b118b1d4..90b4ec094 100644 --- a/ui/app/components/transaction-list-item-icon.js +++ b/ui/app/components/transaction-list-item-icon.js @@ -13,13 +13,34 @@ function TransactionIcon () { TransactionIcon.prototype.render = function () { const { transaction, txParams, isMsg } = this.props + switch (transaction.status) { + case 'unapproved': + return h( !isMsg ? '.unapproved-tx-icon' : 'i.fa.fa-certificate.fa-lg', { + style: { + width: '24px', + }, + }) - if (transaction.status === 'rejected') { - return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { - style: { - width: '24px', - }, - }) + case 'rejected': + return h('i.fa.fa-exclamation-triangle.fa-lg.warning', { + style: { + width: '24px', + }, + }) + + case 'failed': + return h('i.fa.fa-exclamation-triangle.fa-lg.error', { + style: { + width: '24px', + }, + }) + + case 'submitted': + return h('i.fa.fa-ellipsis-h', { + style: { + fontSize: '27px', + }, + }) } if (isMsg) { diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 1b85464e1..44d2dc587 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -8,6 +8,7 @@ const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) const extension = require('../../../app/scripts/lib/extension') +const Tooltip = require('./tooltip') const TransactionIcon = require('./transaction-list-item-icon') const ShiftListItem = require('./shift-list-item') @@ -27,12 +28,11 @@ TransactionListItem.prototype.render = function () { let isLinkable = false const numericNet = parseInt(network) - isLinkable = numericNet === 1 || numericNet === 2 + isLinkable = numericNet === 1 || numericNet === 3 var isMsg = ('msgParams' in transaction) var isTx = ('txParams' in transaction) - var isPending = transaction.status === 'unconfirmed' - + var isPending = transaction.status === 'unapproved' let txParams if (isTx) { txParams = transaction.txParams @@ -41,14 +41,13 @@ TransactionListItem.prototype.render = function () { } const isClickable = ('hash' in transaction && isLinkable) || isPending - return ( h(`.transaction-list-item.flex-row.flex-space-between${isClickable ? '.pointer' : ''}`, { onClick: (event) => { if (isPending) { this.props.showTx(transaction.id) } - + event.stopPropagation() if (!transaction.hash || !isLinkable) return var url = explorerLink(transaction.hash, parseInt(network)) extension.tabs.create({ url }) @@ -58,10 +57,17 @@ TransactionListItem.prototype.render = function () { }, }, [ - // large identicon h('.identicon-wrapper.flex-column.flex-center.select-none', [ - transaction.status === 'unconfirmed' ? h('i.fa.fa-ellipsis-h', {style: { fontSize: '27px' }}) - : h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + h('.pop-hover', { + onClick: (event) => { + event.stopPropagation() + if (!isTx || isPending) return + var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}` + extension.tabs.create({ url }) + }, + }, [ + h(TransactionIcon, { txParams, transaction, isTx, isMsg }), + ]), ]), h('.flex-column', {style: {width: '200px', overflow: 'hidden'}}, [ @@ -77,6 +83,7 @@ TransactionListItem.prototype.render = function () { value: txParams.value, width: '55px', shorten: true, + showFiat: false, style: {fontSize: '15px'}, }) : h('.flex-column'), ]) @@ -127,7 +134,14 @@ function failIfFailed (transaction) { if (transaction.status === 'rejected') { return h('span.error', ' (Rejected)') } - if (transaction.status === 'failed') { - return h('span.error', ' (Failed)') + if (transaction.err) { + + return h(Tooltip, { + title: transaction.err.message, + position: 'bottom', + }, [ + h('span.error', ' (Failed)'), + ]) } + } diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js index 7e1bedb05..3ae953637 100644 --- a/ui/app/components/transaction-list.js +++ b/ui/app/components/transaction-list.js @@ -13,12 +13,13 @@ function TransactionList () { } TransactionList.prototype.render = function () { - const { txsToRender, network, unconfMsgs } = this.props + const { transactions, network, unapprovedMsgs } = this.props + var shapeShiftTxList if (network === '1') { shapeShiftTxList = this.props.shapeShiftTxList } - const transactions = !shapeShiftTxList ? txsToRender.concat(unconfMsgs) : txsToRender.concat(unconfMsgs, shapeShiftTxList) + const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList) .sort((a, b) => b.time - a.time) return ( @@ -55,8 +56,8 @@ TransactionList.prototype.render = function () { }, }, [ - transactions.length - ? transactions.map((transaction, i) => { + txsToRender.length + ? txsToRender.map((transaction, i) => { let key switch (transaction.key) { case 'shapeshift': |