From 9c6dd9ef4953f6e421feb6e6684ef43da26f6b75 Mon Sep 17 00:00:00 2001 From: Frankie Date: Wed, 10 Aug 2016 13:43:01 -0700 Subject: [PATCH 1/8] Create "buy form" add shape shift --- app/scripts/metamask-controller.js | 36 ++- ui/app/account-detail.js | 32 ++- ui/app/actions.js | 169 ++++++++++++ ui/app/app.js | 17 ++ ui/app/components/buy-button-subview.js | 74 ++++++ ui/app/components/coinbase-form.js | 186 +++++++++++++ ui/app/components/shapeshift-form.js | 335 ++++++++++++++++++++++++ ui/app/css/index.css | 110 ++++++++ ui/app/reducers.js | 8 + ui/app/store.js | 8 +- 10 files changed, 966 insertions(+), 9 deletions(-) create mode 100644 ui/app/components/buy-button-subview.js create mode 100644 ui/app/components/coinbase-form.js create mode 100644 ui/app/components/shapeshift-form.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 63970799d..dd43ac2fc 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -6,6 +6,7 @@ const messageManager = require('./lib/message-manager') const HostStore = require('./lib/remote-store.js').HostStore const Web3 = require('web3') const ConfigManager = require('./lib/config-manager') +const extension = require('./lib/extension') module.exports = class MetamaskController { @@ -39,6 +40,7 @@ module.exports = class MetamaskController { setProviderType: this.setProviderType.bind(this), useEtherscanProvider: this.useEtherscanProvider.bind(this), agreeToDisclaimer: this.agreeToDisclaimer.bind(this), + agreeToEthWarning: this.agreeToEthWarning.bind(this), // forward directly to idStore createNewVault: idStore.createNewVault.bind(idStore), recoverFromSeed: idStore.recoverFromSeed.bind(idStore), @@ -55,6 +57,8 @@ module.exports = class MetamaskController { saveAccountLabel: idStore.saveAccountLabel.bind(idStore), tryPassword: idStore.tryPassword.bind(idStore), recoverSeed: idStore.recoverSeed.bind(idStore), + // coinbase + buyEth: this.buyEth.bind(this), } } @@ -161,6 +165,7 @@ module.exports = class MetamaskController { function configToPublic (state) { return { provider: state.provider, + selectedAddress: state.selectedAccount, } } // dump obj into store @@ -236,23 +241,48 @@ module.exports = class MetamaskController { } } + agreeToEthWarning (cb) { + try { + this.configManager.setShouldntShowWarning(true) + cb() + } catch (e) { + cb(e) + } + } + // called from popup setRpcTarget (rpcTarget) { this.configManager.setRpcTarget(rpcTarget) - chrome.runtime.reload() + extension.runtime.reload() this.idStore.getNetwork() } setProviderType (type) { this.configManager.setProviderType(type) - chrome.runtime.reload() + extension.runtime.reload() this.idStore.getNetwork() } useEtherscanProvider () { this.configManager.useEtherscanProvider() - chrome.runtime.reload() + extension.runtime.reload() } + + buyEth (address, amount) { + if (!amount) amount = '5' + + var network = this.idStore._currentState.network + var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` + + if (network === '2') { + url = 'https://testfaucet.metamask.io/' + } + + extension.tabs.create({ + url, + }) + } + } function noop () {} diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 6d50dbd71..cf65cbb7d 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -15,7 +15,7 @@ const ExportAccountView = require('./components/account-export') const ethUtil = require('ethereumjs-util') const EditableLabel = require('./components/editable-label') const Tooltip = require('./components/tooltip') - +const BuyButtonSubview = require('./components/buy-button-subview') module.exports = connect(mapStateToProps)(AccountDetailScreen) function mapStateToProps (state) { @@ -28,6 +28,7 @@ function mapStateToProps (state) { network: state.metamask.network, unconfTxs: valuesFor(state.metamask.unconfTxs), unconfMsgs: valuesFor(state.metamask.unconfMsgs), + isEthWarningConfirmed: state.metamask.isEthConfirmed, } } @@ -170,6 +171,21 @@ AccountDetailScreen.prototype.render = function () { }, }), + h('button', { + onClick: this.buyButtonDeligator.bind(this), + style: { + marginBottom: '20px', + marginRight: '8px', + position: 'absolute', + left: '219px', + }, + }, props.accountDetail.subview === 'buyForm' ? [h('i.fa.fa-arrow-left', { + style: { + width: '22.641px', + height: '14px', + }, + })] : 'BUY'), + h('button', { onClick: () => props.dispatch(actions.showSendPage()), style: { @@ -181,7 +197,7 @@ AccountDetailScreen.prototype.render = function () { ]), ]), - // subview (tx history, pk export confirm) + // subview (tx history, pk export confirm, buy eth warning) h(ReactCSSTransitionGroup, { className: 'css-transition-group', transitionName: 'main', @@ -209,6 +225,8 @@ AccountDetailScreen.prototype.subview = function () { case 'export': var state = extend({key: 'export'}, this.props) return h(ExportAccountView, state) + case 'buyForm': + return h(BuyButtonSubview, extend({key: 'buyForm'}, this.props)) default: return this.transactionList() } @@ -239,3 +257,13 @@ AccountDetailScreen.prototype.transactionList = function () { AccountDetailScreen.prototype.requestAccountExport = function () { this.props.dispatch(actions.requestExportAccount()) } + +AccountDetailScreen.prototype.buyButtonDeligator = function () { + var props = this.props + + if (this.props.accountDetail.subview === 'buyForm') { + props.dispatch(actions.backToAccountDetail(props.address)) + } else { + props.dispatch(actions.buyEthSubview()) + } +} diff --git a/ui/app/actions.js b/ui/app/actions.js index 3e0fe55c0..754ffced8 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -66,6 +66,10 @@ var actions = { showPrivateKey: showPrivateKey, SAVE_ACCOUNT_LABEL: 'SAVE_ACCOUNT_LABEL', saveAccountLabel: saveAccountLabel, + AGREE_TO_ETH_WARNING: 'AGREE_TO_ETH_WARNING', + agreeToEthWarning: agreeToEthWarning, + SHOW_ETH_WARNING: 'SHOW_ETH_WARNING', + showEthWarning: showEthWarning, // tx conf screen COMPLETED_TX: 'COMPLETED_TX', TRANSACTION_ERROR: 'TRANSACTION_ERROR', @@ -106,6 +110,28 @@ var actions = { HIDE_LOADING: 'HIDE_LOADING_INDICATION', showLoadingIndication: showLoadingIndication, hideLoadingIndication: hideLoadingIndication, + // buy Eth with coinbase + BUY_ETH: 'BUY_ETH', + buyEth: buyEth, + buyEthSubview: buyEthSubview, + BUY_ETH_SUBVIEW: 'BUY_ETH_SUBVIEW', + UPDATE_COINBASE_AMOUNT: 'UPDATE_COIBASE_AMOUNT', + updateCoinBaseAmount: updateCoinBaseAmount, + UPDATE_BUY_ADDRESS: 'UPDATE_BUY_ADDRESS', + updateBuyAddress: updateBuyAddress, + COINBASE_SUBVIEW: 'COINBASE_SUBVIEW', + coinBaseSubview: coinBaseSubview, + SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW', + shapeShiftSubview: shapeShiftSubview, + PAIR_UPDATE: 'PAIR_UPDATE', + pairUpdate: pairUpdate, + COIN_SHIFT_REQUEST: 'COIN_SHIFT_REQUEST', + coinShiftRquest: coinShiftRquest, + SHOW_SUB_LOADING_INDICATION: 'SHOW_SUB_LOADING_INDICATION', + showSubLoadingIndication: showSubLoadingIndication, + HIDE_SUB_LOADING_INDICATION: 'HIDE_SUB_LOADING_INDICATION', + hideSubLoadingIndication: hideSubLoadingIndication, + } module.exports = actions @@ -489,6 +515,18 @@ function hideLoadingIndication () { } } +function showSubLoadingIndication () { + return { + type: actions.SHOW_SUB_LOADING_INDICATION, + } +} + +function hideSubLoadingIndication () { + return { + type: actions.HIDE_SUB_LOADING_INDICATION, + } +} + function showWarning (text) { return this.displayWarning(text) } @@ -559,3 +597,134 @@ function showSendPage () { type: actions.SHOW_SEND_PAGE, } } + +function agreeToEthWarning () { + return (dispatch) => { + _accountManager.agreeToEthWarning((err) => { + if (err) { + return dispatch(actions.showEthWarning(err.message)) + } + dispatch({ + type: actions.AGREE_TO_ETH_WARNING, + }) + }) + } +} + +function showEthWarning () { + return { + type: actions.SHOW_ETH_WARNING, + } +} + +function buyEth (address, amount) { + return (dispatch) => { + _accountManager.buyEth(address, amount) + dispatch({ + type: actions.BUY_ETH, + }) + } +} + +function buyEthSubview () { + return { + type: actions.BUY_ETH_SUBVIEW, + } +} + +function updateCoinBaseAmount (value) { + return { + type: actions.UPDATE_COINBASE_AMOUNT, + value, + } +} + +function updateBuyAddress (value) { + return { + type: actions.UPDATE_BUY_ADDRESS, + value, + } +} + +function coinBaseSubview () { + return { + type: actions.COINBASE_SUBVIEW, + } +} + +function pairUpdate (coin) { + return (dispatch) => { + dispatch(actions.showSubLoadingIndication()) + dispatch(actions.hideWarning()) + shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { + dispatch(actions.hideSubLoadingIndication()) + dispatch({ + type: actions.PAIR_UPDATE, + value: { + marketinfo: mktResponse, + }, + }) + }) + } +} + +function shapeShiftSubview (network) { + var pair + network === 'classic' ? pair = 'btc_etc' : pair = 'btc_eth' + + return (dispatch) => { + dispatch(actions.showSubLoadingIndication()) + shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { + shapeShiftRequest('getcoins', {}, (response) => { + dispatch(actions.hideSubLoadingIndication()) + if (mktResponse.error) return dispatch(actions.showWarning(mktResponse.error)) + dispatch({ + type: actions.SHAPESHIFT_SUBVIEW, + value: { + marketinfo: mktResponse, + coinOptions: response, + }, + }) + }) + }) + } +} + +function coinShiftRquest (data) { + return (dispatch) => { + dispatch(actions.showSubLoadingIndication()) + shapeShiftRequest('shift', { method: 'POST', data}, (response) => { + dispatch(actions.hideSubLoadingIndication()) + if (response.error) return dispatch(actions.showWarning(response.error)) + dispatch({ + type: actions.COIN_SHIFT_REQUEST, + value: { + response: response, + }, + }) + }) + } +} +function shapeShiftRequest (query, options, cb) { + var queryResponse, method + !options ? options = {} : null + options.method ? method = options.method : method = 'GET' + + var requestListner = function (request) { + queryResponse = JSON.parse(this.responseText) + cb ? cb(queryResponse) : null + return queryResponse + } + + var shapShiftReq = new XMLHttpRequest() + shapShiftReq.addEventListener('load', requestListner) + shapShiftReq.open(method, `https://shapeshift.io/${query}/${options.pair ? options.pair : ''}`, true) + + if (options.method === 'POST') { + var jsonObj = JSON.stringify(options.data) + shapShiftReq.setRequestHeader('Content-Type', 'application/json') + return shapShiftReq.send(jsonObj) + } else { + return shapShiftReq.send() + } +} diff --git a/ui/app/app.js b/ui/app/app.js index 536b871b3..b8deedc13 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -27,6 +27,7 @@ const MenuDroppo = require('menu-droppo') const DropMenuItem = require('./components/drop-menu-item') const NetworkIndicator = require('./components/network') const Tooltip = require('./components/tooltip') +const EthStoreWarning = require('./eth-store-warning') module.exports = connect(mapStateToProps)(App) @@ -37,6 +38,7 @@ function mapStateToProps (state) { return { // state from plugin isConfirmed: state.metamask.isConfirmed, + isEthConfirmed: state.metamask.isEthConfirmed, isInitialized: state.metamask.isInitialized, isUnlocked: state.metamask.isUnlocked, currentView: state.appState.currentView, @@ -129,6 +131,7 @@ App.prototype.renderAppBar = function () { h(NetworkIndicator, { network: this.props.network, + provider: this.props.provider, onClick: (event) => { event.preventDefault() event.stopPropagation() @@ -201,6 +204,7 @@ App.prototype.renderNetworkDropdown = function () { style: { position: 'absolute', left: 0, + top: '36px', }, innerStyle: { background: 'white', @@ -218,6 +222,16 @@ App.prototype.renderNetworkDropdown = function () { action: () => props.dispatch(actions.setProviderType('mainnet')), icon: h('.menu-icon.diamond'), activeNetworkRender: props.network, + provider: props.provider, + }), + + h(DropMenuItem, { + label: 'Ethereum Classic Network', + closeMenu: () => this.setState({ isNetworkMenuOpen: false }), + action: () => props.dispatch(actions.setProviderType('classic')), + icon: h('.menu-icon.hollow-diamond'), + activeNetworkRender: props.network, + provider: props.provider, }), h(DropMenuItem, { @@ -235,6 +249,7 @@ App.prototype.renderNetworkDropdown = function () { icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }), activeNetworkRender: props.provider.rpcTarget, }), + this.renderCustomOption(props.provider.rpcTarget), ]) } @@ -324,6 +339,8 @@ App.prototype.renderPrimary = function () { // show current view switch (props.currentView.name) { + case 'EthStoreWarning': + return h(EthStoreWarning, {key: 'ethWarning'}) case 'accounts': return h(AccountsScreen, {key: 'accounts'}) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js new file mode 100644 index 000000000..b410630a9 --- /dev/null +++ b/ui/app/components/buy-button-subview.js @@ -0,0 +1,74 @@ +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 CoinbaseForm = require('./coinbase-form') +const ShapeshiftForm = require('./shapeshift-form') + +module.exports = connect(mapStateToProps)(BuyButtonSubview) + +function mapStateToProps (state) { + return { + selectedAccount: state.selectedAccount, + warning: state.appState.warning, + network: state.metamask.network, + provider: state.metamask.provider, + } +} + +inherits(BuyButtonSubview, Component) +function BuyButtonSubview () { + Component.call(this) +} + +BuyButtonSubview.prototype.render = function () { + const props = this.props + const currentForm = props.accountDetail.formView + + return ( + h('span', {key: 'buyForm'}, [ + h('h3.flex-row.text-transform-uppercase', { + style: { + background: '#EBEBEB', + color: '#AEAEAE', + paddingTop: '4px', + justifyContent: 'space-around', + }, + }, [ + h(currentForm.coinbase ? '.activeForm' : '.inactiveForm', { + onClick: () => props.dispatch(actions.coinBaseSubview()), + }, 'Coinbase'), + h(currentForm.shapeshift ? '.activeForm' : '.inactiveForm', { + onClick: () => props.dispatch(actions.shapeShiftSubview(props.provider.type)), + }, 'Shapeshift'), + ]), + this.formVersionSubview(), + ]) + ) +} + +BuyButtonSubview.prototype.formVersionSubview = function () { + if (this.props.network === '1') { + if (this.props.accountDetail.formView.coinbase) { + return h(CoinbaseForm, this.props) + } else if (this.props.accountDetail.formView.shapeshift) { + return h(ShapeshiftForm, this.props) + } + } else { + console.log(this.props.network) + return h('div.flex-column', { + style: { + alignItems: 'center', + margin: '50px', + }, + }, [ + h('h3.text-transform-uppercase', { + style: { + width: '225px', + }, + }, 'In order to access this feature please switch too the Main Network'), + ]) + } +} + diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js new file mode 100644 index 000000000..5ab6b507a --- /dev/null +++ b/ui/app/components/coinbase-form.js @@ -0,0 +1,186 @@ +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 isValidAddress = require('../util').isValidAddress +module.exports = connect(mapStateToProps)(CoinbaseForm) + +function mapStateToProps(state) { + return { + selectedAccount: state.selectedAccount, + warning: state.appState.warning, + } +} + +inherits(CoinbaseForm, Component) + +function CoinbaseForm() { + Component.call(this) +} + +CoinbaseForm.prototype.render = function () { + var props = this.props + var amount = props.accountDetail.amount + var address = props.accountDetail.buyAddress + + return h('.flex-column', { + style: { + margin: '10px', + }, + }, [ + h('.flex-column', { + style: { + alignItems: 'flex-start', + }, + }, [ + h('.flex-column', [ + h('div', 'Address:'), + h('.input-container', { + style: {}, + }, [ + h('input.buy-inputs', { + type: 'text', + style: { + boxSizing: 'border-box', + width: '317px', + height: '20px', + padding: ' 12px 0px 12px 1px ', + }, + defaultValue: address, + onChange: this.handleAddress.bind(this), + }), + h('i.fa.fa-pencil-square-o.edit-text', { + style: { + fontSize: '12px', + color: '#F7861C', + position: 'relative', + bottom: '8px', + right: '11px', + }, + }), + + ]), + ]), + + h('.flex-row', [ + h('div', 'Amount: $'), + h('.input-container', [ + h('input.buy-inputs', { + style: { + width: '3em', + boxSizing: 'border-box', + }, + defaultValue: amount, + onChange: this.handleAmount.bind(this), + }), + h('i.fa.fa-pencil-square-o.edit-text', { + style: { + fontSize: '12px', + color: '#F7861C', + position: 'relative', + bottom: '5px', + right: '11px', + }, + }), + ]), + ]), + ]), + + h('.info-gray', { + style: { + fontSize: '10px', + fontFamily: 'Montserrat Light', + margin: '15px', + lineHeight: '13px', + }, + }, + `there is a USD$ 5 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.`), + + !props.warning ? h('div', { + style: { + width: '340px', + height: '22px', + }, + }) : props.warning && h('span.error.flex-center', props.warning), + + + h('.flex-row', { + style: { + justifyContent: 'space-around', + margin: '33px', + }, + }, [ + h('button', { + onClick: this.toCoinbase.bind(this), + }, 'Continue to Coinbase'), + + h('button', { + onClick: () => props.dispatch(actions.backToAccountDetail(props.accounts.address)), + }, 'Cancel'), + ]), + ]) +} +CoinbaseForm.prototype.handleAmount = function (event) { + this.props.dispatch(actions.updateCoinBaseAmount(event.target.value)) +} +CoinbaseForm.prototype.handleAddress = function (event) { + this.props.dispatch(actions.updateBuyAddress(event.target.value)) +} +CoinbaseForm.prototype.toCoinbase = function () { + var props = this.props + var amount = props.accountDetail.amount + var address = props.accountDetail.buyAddress + var message + + if (isValidAddress(address) && isValidAmountforCoinBase(amount).valid) { + props.dispatch(actions.buyEth(address, props.accountDetail.amount)) + } else if (!isValidAmountforCoinBase(amount).valid) { + message = isValidAmountforCoinBase(amount).message + return props.dispatch(actions.showWarning(message)) + } else { + message = 'Receiving address is invalid.' + return props.dispatch(actions.showWarning(message)) + } +} + +CoinbaseForm.prototype.renderLoading = function () { + + return h('img', { + style: { + width: '27px', + marginRight: '-27px', + }, + src: 'images/loading.svg', + }) +} + +function isValidAmountforCoinBase(amount) { + amount = parseFloat(amount) + + if (amount) { + if (amount <= 5 && amount > 0) { + return { + valid: true, + } + } else if (amount > 5) { + return { + valid: false, + message: 'The amount can not be greater then $5', + } + } else { + return { + valid: false, + message: 'Can not buy amounts less then $0', + } + } + } else { + return { + valid: false, + message: 'The amount entered is not a number', + } + } +} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js new file mode 100644 index 000000000..fea9539b0 --- /dev/null +++ b/ui/app/components/shapeshift-form.js @@ -0,0 +1,335 @@ +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 CopyButton = require('./CopyButton') + +const isValidAddress = require('../util').isValidAddress +module.exports = connect(mapStateToProps)(ShapeshiftForm) + +function mapStateToProps(state) { + return { + selectedAccount: state.selectedAccount, + warning: state.appState.warning, + isSubLoading: state.appState.isSubLoading, + } +} + +inherits(ShapeshiftForm, Component) + +function ShapeshiftForm() { + Component.call(this) +} + +ShapeshiftForm.prototype.render = function () { + const marketinfo = this.props.accountDetail.formView.marketinfo + const coinOptions = this.props.accountDetail.formView.coinOptions + var coin = marketinfo.pair.split('_')[0].toUpperCase() + + return h('.flex-column', { + style: { + margin: '10px', + width: '100%', + alignItems: 'center', + }, + }, [ + h('.flex-row', { + style: { + justifyContent: 'center', + alignItems: 'baseline', + }, + }, [ + h('img', { + src: coinOptions[coin].image, + width: '25px', + height: '25px', + style: { + marginRight: '5px', + }, + }), + + h('.input-container', [ + h('input#fromCoin.buy-inputs.ex-coins', { + type: 'text', + list: 'coinList', + style: { + boxSizing: 'border-box', + }, + onChange: this.handleLiveInput.bind(this), + defaultValue: 'BTC', + }), + + this.renderCoinList(), + + h('i.fa.fa-pencil-square-o.edit-text', { + style: { + fontSize: '12px', + color: '#F7861C', + position: 'relative', + bottom: '23px', + right: '11px', + }, + }), + ]), + + h('.icon-control', [ + h('i.fa.fa-refresh.fa-4.orange', { + style: { + position: 'relative', + bottom: '5px', + right: '5px', + color: '#F7861C', + }, + onClick: this.updateCoin.bind(this), + }), + h('i.fa.fa-chevron-right.fa-4.orange', { + style: { + position: 'relative', + bottom: '5px', + right: '15px', + color: '#F7861C', + }, + onClick: this.updateCoin.bind(this), + }), + ]), + + h('#toCoin.ex-coins', marketinfo.pair.split('_')[1].toUpperCase()), + + h('img', { + src: coinOptions[marketinfo.pair.split('_')[1].toUpperCase()].image, + width: '25px', + height: '25px', + style: { + marginLeft: '5px', + }, + }), + ]), + + this.props.isSubLoading ? this.renderLoading() : null, + + h('.flex-column', { + style: { + width: '235px', + 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(), + ]), + + h(this.activeToggle('.input-container'), { + style: { + width: '100%', + marginTop: '19px', + }, + }, [ + h('div', 'Receiving address:'), + + h('input.buy-inputs', { + type: 'text', + value: this.props.accountDetail.buyAddress, + onChange: this.handleAddress.bind(this), + style: { + boxSizing: 'border-box', + width: '325px', + height: '20px', + padding: ' 5px ', + }, + }), + + h('i.fa.fa-pencil-square-o.edit-text', { + style: { + fontSize: '12px', + color: '#F7861C', + position: 'relative', + bottom: '5px', + right: '11px', + }, + }), + ]), + h(this.activeToggle('.input-container'), { + style: { + width: '100%', + }, + }, [ + h('div', `${coin} Address:`), + + h('input#fromCoinAddress.buy-inputs', { + type: 'text', + placeholder: `Your ${coin} Refund Address`, + style: { + boxSizing: 'border-box', + width: '235px', + height: '20px', + padding: ' 5px ', + }, + }), + + h('i.fa.fa-pencil-square-o.edit-text', { + style: { + fontSize: '12px', + color: '#F7861C', + position: 'relative', + bottom: '5px', + right: '11px', + }, + }), + + h('button', { + onClick: this.shift.bind(this), + }, + 'Submit'), + ]), + ]) +} + +ShapeshiftForm.prototype.shift = function () { + var withdrawal = this.props.accountDetail.buyAddress + var returnAddress = document.getElementById('fromCoinAddress').value + var pair = this.props.accountDetail.formView.marketinfo.pair + var data = { + 'withdrawal': withdrawal, + 'pair': pair, + 'returnAddress': returnAddress, + } + + if (isValidAddress(withdrawal)) { + this.props.dispatch(actions.coinShiftRquest(data)) + } +} + +ShapeshiftForm.prototype.renderCoinList = function () { + var list = Object.keys(this.props.accountDetail.formView.coinOptions).map((item) => { + return h('option', { + value: item, + }, item) + }) + + return h('datalist#coinList', { + onClick: (event) => { + event.preventDefault() + }, + }, list) +} + +ShapeshiftForm.prototype.updateCoin = function (event) { + event.preventDefault() + const props = this.props + var coinOptions = this.props.accountDetail.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.showWarning(message)) + } else { + return props.dispatch(actions.pairUpdate(coin)) + } +} + +ShapeshiftForm.prototype.handleLiveInput = function () { + const props = this.props + var coinOptions = this.props.accountDetail.formView.coinOptions + var coin = document.getElementById('fromCoin').value + + if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { + return null + } else { + return props.dispatch(actions.pairUpdate(coin)) + } +} + +ShapeshiftForm.prototype.renderInfo = function () { + const marketinfo = this.props.accountDetail.formView.marketinfo + const coinOptions = this.props.accountDetail.formView.coinOptions + var coin = marketinfo.pair.split('_')[0].toUpperCase() + const request = this.props.accountDetail.formView.response + + if (!request) { + return h('span', [ + h('h3.flex-row.text-transform-uppercase', { + style: { + color: '#AEAEAE', + paddingTop: '4px', + justifyContent: 'space-around', + textAlign: 'center', + fontSize: '14px', + }, + }, `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}`]), + ]) + } else { + return h('.flex-column', { + style: { + width: '229px', + height: '82px', + }, + }, [ + h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]), + h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]), + h('div', { + style: { + fontSize: '12px', + lineHeight: '16px', + marginTop: '4px', + color: '#F7861C', + }, + }, `Deposit your ${request.depositType} to the address bellow:`), + h('.flex-row', { + style: { + position: 'relative', + right: '38px', + }, + }, [ + h('div', { + style: { + fontSize: '13px', + }, + }, request.deposit), + h(CopyButton, { + value: request.deposit, + }), + ]), + ]) + } +} + +ShapeshiftForm.prototype.handleAddress = function (event) { + this.props.dispatch(actions.updateBuyAddress(event.target.value)) +} + +ShapeshiftForm.prototype.activeToggle = function (elementType) { + if (!this.props.accountDetail.formView.response || this.props.warning) return elementType + return `${elementType}.inactive` +} + +ShapeshiftForm.prototype.renderLoading = function () { + return h('span', { + style: { + position: 'absolute', + left: '70px', + bottom: '138px', + background: 'transparent', + width: '229px', + height: '82px', + display: 'flex', + justifyContent: 'center', + }, + }, [ + h('img', { + style: { + width: '60px', + }, + src: 'images/loading.svg', + }), + ]) +} diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 3f52b6ed4..f31bf5aaa 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -151,12 +151,14 @@ textarea.twelve-word-phrase { .network-name { position: absolute; top: 8px; + left: 60px; width: 5.2em; line-height: 9px; text-rendering: geometricPrecision; } .check { + margin-left: 7px; color: #F7861C; flex: 1 0 auto; display: flex; @@ -463,3 +465,111 @@ input.large-input { display: inline-block; padding-left: 5px; } + +/* buy eth warning screen */ + +.eth-warning{ + transition: opacity 400ms ease-in, transform 400ms ease-in; +} + +.buy-subview{ + transition: opacity 400ms ease-in, transform 400ms ease-in; +} + +.input-container:hover .edit-text{ + visibility: visible; +} + +.buy-inputs{ + font-family: 'Montserrat Light'; + font-size: 13px; + height: 20px; + background: transparent; + box-sizing: border-box; + border: solid; + border-color: transparent; + border-width: 0.5px; + border-radius: 2px; + +} +.input-container:hover .buy-inputs{ + box-sizing: inherit; + border: solid; + border-color: #F7861C; + border-width: 0.5px; + border-radius: 2px; +} + +.buy-inputs:focus{ + border: solid; + border-color: #F7861C; + border-width: 0.5px; + border-radius: 2px; +} + +.activeForm { + background: #F7F7F7; + border: none; + border-radius: 8px 8px 0px 0px; + width: 50%; + text-align: center; + padding-bottom: 4px; + +} + +.inactiveForm { + border: none; + border-radius: 8px 8px 0px 0px; + width: 50%; + text-align: center; + padding-bottom: 4px; +} + +.ex-coins { + font-family: 'Montserrat Regular'; + text-transform: uppercase; + text-align: center; + font-size: 33px; + width: 118px; + height: 42px; + padding: 1px; + color: #4D4D4D; +} + +.marketinfo{ + font-family: 'Montserrat light'; + color: #AEAEAE; + font-size: 12px; + line-height: 14px; +} + +#fromCoin::-webkit-calendar-picker-indicator { + display: none; +} + +#coinList { + width: 400px; + height: 500px; + overflow: scroll; +} + +.icon-control .fa-refresh{ + visibility: hidden; +} + +.icon-control:hover .fa-refresh{ + visibility: visible; +} + +.icon-control:hover .fa-chevron-right{ + visibility: hidden; +} + +.inactive { + color: #AEAEAE; +} + +.inactive button{ + background: #AEAEAE; + color: white; +} diff --git a/ui/app/reducers.js b/ui/app/reducers.js index 9243ddba4..a691cf614 100644 --- a/ui/app/reducers.js +++ b/ui/app/reducers.js @@ -7,6 +7,8 @@ const reduceIdentities = require('./reducers/identities') const reduceMetamask = require('./reducers/metamask') const reduceApp = require('./reducers/app') +window.METAMASK_CACHED_LOG_STATE = null + module.exports = rootReducer function rootReducer (state, action) { @@ -35,5 +37,11 @@ function rootReducer (state, action) { state.appState = reduceApp(state, action) + window.METAMASK_CACHED_LOG_STATE = state return state } + +window.logState = function() { + var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, null, 2) + console.log(stateString) +} diff --git a/ui/app/store.js b/ui/app/store.js index ab6422e73..363a51cea 100644 --- a/ui/app/store.js +++ b/ui/app/store.js @@ -1,16 +1,16 @@ const createStore = require('redux').createStore const applyMiddleware = require('redux').applyMiddleware const thunkMiddleware = require('redux-thunk') -const createLogger = require('redux-logger') +// const createLogger = require('redux-logger') const rootReducer = require('./reducers') module.exports = configureStore -const loggerMiddleware = createLogger() +// const loggerMiddleware = createLogger() const createStoreWithMiddleware = applyMiddleware( - thunkMiddleware, - loggerMiddleware + thunkMiddleware + // // loggerMiddleware )(createStore) function configureStore (initialState) { From 4ace425a9c4a5112692d1e9269d3074a0987e5e9 Mon Sep 17 00:00:00 2001 From: Frankie Date: Wed, 10 Aug 2016 13:51:14 -0700 Subject: [PATCH 2/8] Fix merge mess --- ui/app/account-detail.js | 8 --- ui/app/actions.js | 1 - ui/app/eth-store-warning.js | 7 ++- ui/app/reducers/app.js | 106 +++++++++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 14 deletions(-) diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index 814d8bab0..cf65cbb7d 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -172,27 +172,19 @@ AccountDetailScreen.prototype.render = function () { }), h('button', { -<<<<<<< HEAD onClick: this.buyButtonDeligator.bind(this), -======= - onClick: () => props.dispatch(actions.buyEth(selected)), ->>>>>>> master style: { marginBottom: '20px', marginRight: '8px', position: 'absolute', left: '219px', }, -<<<<<<< HEAD }, props.accountDetail.subview === 'buyForm' ? [h('i.fa.fa-arrow-left', { style: { width: '22.641px', height: '14px', }, })] : 'BUY'), -======= - }, 'BUY'), ->>>>>>> master h('button', { onClick: () => props.dispatch(actions.showSendPage()), diff --git a/ui/app/actions.js b/ui/app/actions.js index bdf100040..52c03f16f 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -624,7 +624,6 @@ function buyEth (address, amount) { }) } } -<<<<<<< HEAD function buyEthSubview () { return { diff --git a/ui/app/eth-store-warning.js b/ui/app/eth-store-warning.js index 7fe54a309..55274996b 100644 --- a/ui/app/eth-store-warning.js +++ b/ui/app/eth-store-warning.js @@ -35,9 +35,10 @@ EthStoreWarning.prototype.render = function () { margin: '10px 10px 10px 10px', }, }, - `MetaMask is currently in beta - - exercise caution while handling - and storing your ether. + `The MetaMask team would like to + remind you that MetaMask is currently in beta - so + don't store large + amounts of ether in MetaMask. `), h('i.fa.fa-exclamation-triangle.fa-4', { diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index a9d6e4ff0..3b23ce005 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -317,6 +317,15 @@ function reduceApp (state, action) { isLoading: false, }) + case actions.SHOW_SUB_LOADING_INDICATION: + return extend(appState, { + isSubLoading: true, + }) + + case actions.HIDE_SUB_LOADING_INDICATION: + return extend(appState, { + isSubLoading: false, + }) case actions.CLEAR_SEED_WORD_CACHE: return extend(appState, { transForward: true, @@ -369,7 +378,7 @@ function reduceApp (state, action) { }, }) - case actions.SHOW_ETH_WARNING: + case actions.BUY_ETH_SUBVIEW: return extend(appState, { transForward: true, currentView: { @@ -377,7 +386,100 @@ function reduceApp (state, action) { context: appState.currentView.context, }, accountDetail: { - subview: 'buy-eth-warning', + subview: 'buyForm', + amount: '5.00', + buyAddress: appState.currentView.context, + formView: { + coinbase: true, + shapeshift: false, + }, + }, + }) + + case actions.UPDATE_BUY_ADDRESS: + return extend(appState, { + accountDetail: { + subview: 'buyForm', + formView: { + coinbase: true, + shapeshift: false, + }, + buyAddress: action.value, + amount: appState.accountDetail.amount, + }, + }) + + case actions.UPDATE_COINBASE_AMOUNT: + return extend(appState, { + accountDetail: { + subview: 'buyForm', + formView: { + coinbase: true, + shapeshift: false, + }, + buyAddress: appState.accountDetail.buyAddress, + amount: action.value, + }, + }) + + case actions.COINBASE_SUBVIEW: + return extend(appState, { + accountDetail: { + subview: 'buyForm', + formView: { + coinbase: true, + shapeshift: false, + }, + buyAddress: appState.accountDetail.buyAddress, + amount: appState.accountDetail.amount, + }, + }) + + case actions.SHAPESHIFT_SUBVIEW: + return extend(appState, { + accountDetail: { + subview: 'buyForm', + formView: { + coinbase: false, + shapeshift: true, + marketinfo: action.value.marketinfo, + coinOptions: action.value.coinOptions, + }, + buyAddress: appState.accountDetail.buyAddress, + amount: appState.accountDetail.amount, + }, + }) + + case actions.PAIR_UPDATE: + return extend(appState, { + accountDetail: { + subview: 'buyForm', + formView: { + coinbase: false, + shapeshift: true, + marketinfo: action.value.marketinfo, + coinOptions: appState.accountDetail.formView.coinOptions, + }, + buyAddress: appState.accountDetail.buyAddress, + amount: appState.accountDetail.amount, + warning: null, + }, + }) + + case actions.COIN_SHIFT_REQUEST: + return extend(appState, { + accountDetail: { + subview: 'buyForm', + formView: { + coinbase: false, + shapeshift: true, + marketinfo: appState.accountDetail.formView.marketinfo, + coinOptions: appState.accountDetail.formView.coinOptions, + response: action.value.response, + }, + buyAddress: appState.accountDetail.buyAddress, + amount: appState.accountDetail.amount, + warning: null, }, }) default: From c8b2826441d03dd2fb525ce6f7a89e3873117a37 Mon Sep 17 00:00:00 2001 From: Frankie Date: Wed, 10 Aug 2016 15:07:10 -0700 Subject: [PATCH 3/8] Fix typo for copyButton --- ui/app/components/shapeshift-form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index fea9539b0..116842043 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -3,7 +3,7 @@ const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../actions') -const CopyButton = require('./CopyButton') +const CopyButton = require('./copyButton') const isValidAddress = require('../util').isValidAddress module.exports = connect(mapStateToProps)(ShapeshiftForm) From 2fc26fb264ba0df7e4fc60128c6cbe19d3141beb Mon Sep 17 00:00:00 2001 From: Frankie Date: Wed, 10 Aug 2016 18:53:11 -0700 Subject: [PATCH 4/8] Even out some of the margins --- ui/app/components/shapeshift-form.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 116842043..9c9ec2a05 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -29,7 +29,7 @@ ShapeshiftForm.prototype.render = function () { return h('.flex-column', { style: { - margin: '10px', + marginTop: '10px', width: '100%', alignItems: 'center', }, @@ -126,8 +126,9 @@ ShapeshiftForm.prototype.render = function () { h(this.activeToggle('.input-container'), { style: { + padding: '10px', + paddingBottom: '0px', width: '100%', - marginTop: '19px', }, }, [ h('div', 'Receiving address:'), @@ -156,6 +157,7 @@ ShapeshiftForm.prototype.render = function () { ]), h(this.activeToggle('.input-container'), { style: { + padding: '10px', width: '100%', }, }, [ From b4c9a5225947f9aadac5fd1bb23fde64740d774a Mon Sep 17 00:00:00 2001 From: Frankie Date: Fri, 12 Aug 2016 15:41:59 -0700 Subject: [PATCH 5/8] Change buy forms so that they are their own view and add Qr-code --- ui/app/account-detail.js | 4 +- ui/app/actions.js | 46 +++++-- ui/app/app.js | 4 +- ui/app/components/buy-button-subview.js | 51 ++++++- ui/app/components/coinbase-form.js | 44 ++---- ui/app/components/qr-code.js | 50 +++++++ ui/app/components/shapeshift-form.js | 172 ++++++++++-------------- ui/app/css/index.css | 9 ++ ui/app/reducers/app.js | 60 ++++----- 9 files changed, 256 insertions(+), 184 deletions(-) create mode 100644 ui/app/components/qr-code.js diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js index cf65cbb7d..0053935f5 100644 --- a/ui/app/account-detail.js +++ b/ui/app/account-detail.js @@ -172,7 +172,7 @@ AccountDetailScreen.prototype.render = function () { }), h('button', { - onClick: this.buyButtonDeligator.bind(this), + onClick: () => props.dispatch(actions.buyEthView(selected)), style: { marginBottom: '20px', marginRight: '8px', @@ -264,6 +264,6 @@ AccountDetailScreen.prototype.buyButtonDeligator = function () { if (this.props.accountDetail.subview === 'buyForm') { props.dispatch(actions.backToAccountDetail(props.address)) } else { - props.dispatch(actions.buyEthSubview()) + props.dispatch(actions.buyEthView()) } } diff --git a/ui/app/actions.js b/ui/app/actions.js index 52c03f16f..b6b5e684b 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -113,8 +113,8 @@ var actions = { // buy Eth with coinbase BUY_ETH: 'BUY_ETH', buyEth: buyEth, - buyEthSubview: buyEthSubview, - BUY_ETH_SUBVIEW: 'BUY_ETH_SUBVIEW', + buyEthView: buyEthView, + BUY_ETH_VIEW: 'BUY_ETH_VIEW', UPDATE_COINBASE_AMOUNT: 'UPDATE_COIBASE_AMOUNT', updateCoinBaseAmount: updateCoinBaseAmount, UPDATE_BUY_ADDRESS: 'UPDATE_BUY_ADDRESS', @@ -125,12 +125,14 @@ var actions = { shapeShiftSubview: shapeShiftSubview, PAIR_UPDATE: 'PAIR_UPDATE', pairUpdate: pairUpdate, - COIN_SHIFT_REQUEST: 'COIN_SHIFT_REQUEST', coinShiftRquest: coinShiftRquest, SHOW_SUB_LOADING_INDICATION: 'SHOW_SUB_LOADING_INDICATION', showSubLoadingIndication: showSubLoadingIndication, HIDE_SUB_LOADING_INDICATION: 'HIDE_SUB_LOADING_INDICATION', hideSubLoadingIndication: hideSubLoadingIndication, +// QR STUFF: + SHOW_QR: 'SHOW_QR', + getQr: getQr, } module.exports = actions @@ -625,9 +627,10 @@ function buyEth (address, amount) { } } -function buyEthSubview () { +function buyEthView (address) { return { - type: actions.BUY_ETH_SUBVIEW, + type: actions.BUY_ETH_VIEW, + value: address, } } @@ -691,19 +694,32 @@ function shapeShiftSubview (network) { function coinShiftRquest (data) { return (dispatch) => { - dispatch(actions.showSubLoadingIndication()) + dispatch(actions.showLoadingIndication()) shapeShiftRequest('shift', { method: 'POST', data}, (response) => { - dispatch(actions.hideSubLoadingIndication()) + if (response.error) return dispatch(actions.showWarning(response.error)) + var message = `Deposit your ${response.depositType} to the address bellow:` + dispatch(actions.getQr(response.deposit, '125x125', message)) + }) + } +} + +function getQr (data, size, message) { + return (dispatch) => { + qrRequest(data, size, (response) => { + dispatch(actions.hideLoadingIndication()) if (response.error) return dispatch(actions.showWarning(response.error)) dispatch({ - type: actions.COIN_SHIFT_REQUEST, + type: actions.SHOW_QR, value: { - response: response, + qr: response, + message: message, + data: data, }, }) }) } } + function shapeShiftRequest (query, options, cb) { var queryResponse, method !options ? options = {} : null @@ -727,3 +743,15 @@ function shapeShiftRequest (query, options, cb) { return shapShiftReq.send() } } + +function qrRequest (data, size, cb) { + var requestListner = function (request) { + cb ? cb(this.responseText) : null + return this.responseText + } + + var qrReq = new XMLHttpRequest() + qrReq.addEventListener('load', requestListner) + qrReq.open('GET', `https://api.qrserver.com/v1/create-qr-code/?size=${size}&format=svg&data=${data}`, true) + qrReq.send() +} diff --git a/ui/app/app.js b/ui/app/app.js index cc616fb7c..4cc32bf9b 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -28,7 +28,7 @@ const DropMenuItem = require('./components/drop-menu-item') const NetworkIndicator = require('./components/network') const Tooltip = require('./components/tooltip') const EthStoreWarning = require('./eth-store-warning') - +const BuyView = require('./components/buy-button-subview') module.exports = connect(mapStateToProps)(App) inherits(App, Component) @@ -366,6 +366,8 @@ App.prototype.renderPrimary = function () { case 'createVault': return h(CreateVaultScreen, {key: 'createVault'}) + case 'buyEth': + return h(BuyView, {key: 'buyEthView'}) default: return h(AccountDetailScreen, {key: 'account-detail'}) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index b410630a9..3f13878df 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -5,6 +5,7 @@ const connect = require('react-redux').connect const actions = require('../actions') const CoinbaseForm = require('./coinbase-form') const ShapeshiftForm = require('./shapeshift-form') +const extension = require('../../../app/scripts/lib/extension') module.exports = connect(mapStateToProps)(BuyButtonSubview) @@ -12,6 +13,7 @@ function mapStateToProps (state) { return { selectedAccount: state.selectedAccount, warning: state.appState.warning, + buyView: state.appState.buyView, network: state.metamask.network, provider: state.metamask.provider, } @@ -24,10 +26,26 @@ function BuyButtonSubview () { BuyButtonSubview.prototype.render = function () { const props = this.props - const currentForm = props.accountDetail.formView + const currentForm = props.buyView.formView return ( - h('span', {key: 'buyForm'}, [ + h('.buy-eth-section', [ + // back button + h('.flex-row', { + style: { + alignItems: 'center', + justifyContent: 'center', + }, + }, [ + h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { + onClick: () => props.dispatch(actions.backToAccountDetail(props.selectedAccount)), + style: { + position: 'absolute', + left: '10px', + }, + }), + h('h2.page-subtitle', 'Buy Eth'), + ]), h('h3.flex-row.text-transform-uppercase', { style: { background: '#EBEBEB', @@ -39,9 +57,31 @@ BuyButtonSubview.prototype.render = function () { h(currentForm.coinbase ? '.activeForm' : '.inactiveForm', { 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', { 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(), ]) @@ -50,9 +90,9 @@ BuyButtonSubview.prototype.render = function () { BuyButtonSubview.prototype.formVersionSubview = function () { if (this.props.network === '1') { - if (this.props.accountDetail.formView.coinbase) { + if (this.props.buyView.formView.coinbase) { return h(CoinbaseForm, this.props) - } else if (this.props.accountDetail.formView.shapeshift) { + } else if (this.props.buyView.formView.shapeshift) { return h(ShapeshiftForm, this.props) } } else { @@ -72,3 +112,6 @@ BuyButtonSubview.prototype.formVersionSubview = function () { } } +BuyButtonSubview.prototype.navigateTo = function (url) { + extension.tabs.create({ url }) +} diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js index 5ab6b507a..efd05ec96 100644 --- a/ui/app/components/coinbase-form.js +++ b/ui/app/components/coinbase-form.js @@ -22,12 +22,13 @@ function CoinbaseForm() { CoinbaseForm.prototype.render = function () { var props = this.props - var amount = props.accountDetail.amount - var address = props.accountDetail.buyAddress + var amount = props.buyView.amount + var address = props.buyView.buyAddress return h('.flex-column', { style: { - margin: '10px', + // margin: '10px', + padding: '25px', }, }, [ h('.flex-column', { @@ -35,35 +36,10 @@ CoinbaseForm.prototype.render = function () { alignItems: 'flex-start', }, }, [ - h('.flex-column', [ + h('.flex-row', [ h('div', 'Address:'), - h('.input-container', { - style: {}, - }, [ - h('input.buy-inputs', { - type: 'text', - style: { - boxSizing: 'border-box', - width: '317px', - height: '20px', - padding: ' 12px 0px 12px 1px ', - }, - defaultValue: address, - onChange: this.handleAddress.bind(this), - }), - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'relative', - bottom: '8px', - right: '11px', - }, - }), - - ]), + h('.ellip-address', address), ]), - h('.flex-row', [ h('div', 'Amount: $'), h('.input-container', [ @@ -119,7 +95,7 @@ CoinbaseForm.prototype.render = function () { }, 'Continue to Coinbase'), h('button', { - onClick: () => props.dispatch(actions.backToAccountDetail(props.accounts.address)), + onClick: () => props.dispatch(actions.backTobuyView(props.accounts.address)), }, 'Cancel'), ]), ]) @@ -132,12 +108,12 @@ CoinbaseForm.prototype.handleAddress = function (event) { } CoinbaseForm.prototype.toCoinbase = function () { var props = this.props - var amount = props.accountDetail.amount - var address = props.accountDetail.buyAddress + var amount = props.buyView.amount + var address = props.buyView.buyAddress var message if (isValidAddress(address) && isValidAmountforCoinBase(amount).valid) { - props.dispatch(actions.buyEth(address, props.accountDetail.amount)) + props.dispatch(actions.buyEth(address, props.buyView.amount)) } else if (!isValidAmountforCoinBase(amount).valid) { message = isValidAmountforCoinBase(amount).message return props.dispatch(actions.showWarning(message)) diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js new file mode 100644 index 000000000..765322239 --- /dev/null +++ b/ui/app/components/qr-code.js @@ -0,0 +1,50 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const CopyButton = require('./copyButton') + +module.exports = connect(mapStateToProps)(QrCodeView) + +function mapStateToProps (state) { + return { + Qr: state.appState.Qr, + buyView: state.appState.buyView, + } +} + +inherits(QrCodeView, Component) + +function QrCodeView () { + Component.call(this) +} + +QrCodeView.prototype.render = function () { + var props = this.props + var Qr = props.Qr + return h('.main-container.flex-column', { + style: { + justifyContent: 'center', + padding: '45px', + alignItems: 'center', + }, + }, [ + h('h3', Qr.message), + h('#qr-container.flex-column', { + key: 'qr', + style: { + marginTop: '25px', + marginBottom: '15px', + }, + dangerouslySetInnerHTML: { + __html: Qr.image, + }, + }), + h('.flex-row', [ + h('h3.ellip-address', Qr.data), + h(CopyButton, { + value: Qr.data, + }), + ]), + ]) +} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 9c9ec2a05..52bacf798 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -2,9 +2,9 @@ const Component = require('react').Component const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect +const ReactCSSTransitionGroup = require('react-addons-css-transition-group') const actions = require('../actions') -const CopyButton = require('./copyButton') - +const Qr = require('./qr-code') const isValidAddress = require('../util').isValidAddress module.exports = connect(mapStateToProps)(ShapeshiftForm) @@ -13,23 +13,36 @@ function mapStateToProps(state) { selectedAccount: state.selectedAccount, warning: state.appState.warning, isSubLoading: state.appState.isSubLoading, + qrRequested: state.appState.qrRequested, } } inherits(ShapeshiftForm, Component) -function ShapeshiftForm() { +function ShapeshiftForm () { Component.call(this) } - ShapeshiftForm.prototype.render = function () { - const marketinfo = this.props.accountDetail.formView.marketinfo - const coinOptions = this.props.accountDetail.formView.coinOptions + return h(ReactCSSTransitionGroup, { + className: 'css-transition-group', + transitionName: 'main', + transitionEnterTimeout: 300, + transitionLeaveTimeout: 300, + }, [ + this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain(), + ]) + +} + +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: { - marginTop: '10px', + // marginTop: '10px', + padding: '25px', width: '100%', alignItems: 'center', }, @@ -67,8 +80,8 @@ ShapeshiftForm.prototype.render = function () { fontSize: '12px', color: '#F7861C', position: 'relative', - bottom: '23px', - right: '11px', + bottom: '48px', + left: '106px', }, }), ]), @@ -78,7 +91,7 @@ ShapeshiftForm.prototype.render = function () { style: { position: 'relative', bottom: '5px', - right: '5px', + left: '5px', color: '#F7861C', }, onClick: this.updateCoin.bind(this), @@ -86,8 +99,8 @@ ShapeshiftForm.prototype.render = function () { h('i.fa.fa-chevron-right.fa-4.orange', { style: { position: 'relative', - bottom: '5px', - right: '15px', + bottom: '26px', + left: '10px', color: '#F7861C', }, onClick: this.updateCoin.bind(this), @@ -107,7 +120,6 @@ ShapeshiftForm.prototype.render = function () { ]), this.props.isSubLoading ? this.renderLoading() : null, - h('.flex-column', { style: { width: '235px', @@ -124,40 +136,21 @@ ShapeshiftForm.prototype.render = function () { this.props.warning) : this.renderInfo(), ]), - h(this.activeToggle('.input-container'), { + h('.flex-row', { style: { padding: '10px', - paddingBottom: '0px', + paddingBottom: '2px', width: '100%', }, }, [ h('div', 'Receiving address:'), - - h('input.buy-inputs', { - type: 'text', - value: this.props.accountDetail.buyAddress, - onChange: this.handleAddress.bind(this), - style: { - boxSizing: 'border-box', - width: '325px', - height: '20px', - padding: ' 5px ', - }, - }), - - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'relative', - bottom: '5px', - right: '11px', - }, - }), + h('.ellip-address', this.props.buyView.buyAddress), ]), + h(this.activeToggle('.input-container'), { style: { padding: '10px', + paddingTop: '0px', width: '100%', }, }, [ @@ -168,7 +161,7 @@ ShapeshiftForm.prototype.render = function () { placeholder: `Your ${coin} Refund Address`, style: { boxSizing: 'border-box', - width: '235px', + width: '278px', height: '20px', padding: ' 5px ', }, @@ -183,19 +176,27 @@ ShapeshiftForm.prototype.render = function () { right: '11px', }, }), - - h('button', { - onClick: this.shift.bind(this), - }, - 'Submit'), + h('.flex-row', { + style: { + justifyContent: 'flex-end', + }, + }, [ + h('button', { + onClick: this.shift.bind(this), + style: { + marginTop: '10px', + }, + }, + 'Submit'), + ]), ]), ]) } ShapeshiftForm.prototype.shift = function () { - var withdrawal = this.props.accountDetail.buyAddress + var withdrawal = this.props.buyView.buyAddress var returnAddress = document.getElementById('fromCoinAddress').value - var pair = this.props.accountDetail.formView.marketinfo.pair + var pair = this.props.buyView.formView.marketinfo.pair var data = { 'withdrawal': withdrawal, 'pair': pair, @@ -208,7 +209,7 @@ ShapeshiftForm.prototype.shift = function () { } ShapeshiftForm.prototype.renderCoinList = function () { - var list = Object.keys(this.props.accountDetail.formView.coinOptions).map((item) => { + var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => { return h('option', { value: item, }, item) @@ -224,7 +225,7 @@ ShapeshiftForm.prototype.renderCoinList = function () { ShapeshiftForm.prototype.updateCoin = function (event) { event.preventDefault() const props = this.props - var coinOptions = this.props.accountDetail.formView.coinOptions + var coinOptions = this.props.buyView.formView.coinOptions var coin = document.getElementById('fromCoin').value if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { @@ -237,7 +238,7 @@ ShapeshiftForm.prototype.updateCoin = function (event) { ShapeshiftForm.prototype.handleLiveInput = function () { const props = this.props - var coinOptions = this.props.accountDetail.formView.coinOptions + var coinOptions = this.props.buyView.formView.coinOptions var coin = document.getElementById('fromCoin').value if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { @@ -248,61 +249,30 @@ ShapeshiftForm.prototype.handleLiveInput = function () { } ShapeshiftForm.prototype.renderInfo = function () { - const marketinfo = this.props.accountDetail.formView.marketinfo - const coinOptions = this.props.accountDetail.formView.coinOptions + const marketinfo = this.props.buyView.formView.marketinfo + const coinOptions = this.props.buyView.formView.coinOptions var coin = marketinfo.pair.split('_')[0].toUpperCase() - const request = this.props.accountDetail.formView.response - if (!request) { - return h('span', [ - h('h3.flex-row.text-transform-uppercase', { - style: { - color: '#AEAEAE', - paddingTop: '4px', - justifyContent: 'space-around', - textAlign: 'center', - fontSize: '14px', - }, - }, `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}`]), - ]) - } else { - return h('.flex-column', { + return h('span', { + style: { + marginTop: '15px', + marginBottom: '15px', + }, + }, [ + h('h3.flex-row.text-transform-uppercase', { style: { - width: '229px', - height: '82px', + color: '#AEAEAE', + paddingTop: '4px', + justifyContent: 'space-around', + textAlign: 'center', + fontSize: '14px', }, - }, [ - h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]), - h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]), - h('div', { - style: { - fontSize: '12px', - lineHeight: '16px', - marginTop: '4px', - color: '#F7861C', - }, - }, `Deposit your ${request.depositType} to the address bellow:`), - h('.flex-row', { - style: { - position: 'relative', - right: '38px', - }, - }, [ - h('div', { - style: { - fontSize: '13px', - }, - }, request.deposit), - h(CopyButton, { - value: request.deposit, - }), - ]), - ]) - } + }, `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.handleAddress = function (event) { @@ -310,7 +280,7 @@ ShapeshiftForm.prototype.handleAddress = function (event) { } ShapeshiftForm.prototype.activeToggle = function (elementType) { - if (!this.props.accountDetail.formView.response || this.props.warning) return elementType + if (!this.props.buyView.formView.response || this.props.warning) return elementType return `${elementType}.inactive` } @@ -319,7 +289,7 @@ ShapeshiftForm.prototype.renderLoading = function () { style: { position: 'absolute', left: '70px', - bottom: '138px', + bottom: '194px', background: 'transparent', width: '229px', height: '82px', diff --git a/ui/app/css/index.css b/ui/app/css/index.css index f31bf5aaa..3b385ad85 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -573,3 +573,12 @@ input.large-input { background: #AEAEAE; color: white; } + +.ellip-address { + overflow: hidden; + text-overflow: ellipsis; + width: 5em; + font-size: 14px; + font-family: "Montserrat Light"; + margin-left: 5px; +} diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 3b23ce005..95b60f929 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -378,17 +378,17 @@ function reduceApp (state, action) { }, }) - case actions.BUY_ETH_SUBVIEW: + case actions.BUY_ETH_VIEW: return extend(appState, { transForward: true, currentView: { - name: 'accountDetail', + name: 'buyEth', context: appState.currentView.context, }, - accountDetail: { + buyView: { subview: 'buyForm', amount: '5.00', - buyAddress: appState.currentView.context, + buyAddress: action.value, formView: { coinbase: true, shapeshift: false, @@ -398,46 +398,46 @@ function reduceApp (state, action) { case actions.UPDATE_BUY_ADDRESS: return extend(appState, { - accountDetail: { + buyView: { subview: 'buyForm', formView: { - coinbase: true, - shapeshift: false, + coinbase: appState.buyView.formView.coinbase, + shapeshift: appState.buyView.formView.shapeshift, }, buyAddress: action.value, - amount: appState.accountDetail.amount, + amount: appState.buyView.amount, }, }) case actions.UPDATE_COINBASE_AMOUNT: return extend(appState, { - accountDetail: { + buyView: { subview: 'buyForm', formView: { coinbase: true, shapeshift: false, }, - buyAddress: appState.accountDetail.buyAddress, + buyAddress: appState.buyView.buyAddress, amount: action.value, }, }) case actions.COINBASE_SUBVIEW: return extend(appState, { - accountDetail: { + buyView: { subview: 'buyForm', formView: { coinbase: true, shapeshift: false, }, - buyAddress: appState.accountDetail.buyAddress, - amount: appState.accountDetail.amount, + buyAddress: appState.buyView.buyAddress, + amount: appState.buyView.amount, }, }) case actions.SHAPESHIFT_SUBVIEW: return extend(appState, { - accountDetail: { + buyView: { subview: 'buyForm', formView: { coinbase: false, @@ -445,41 +445,35 @@ function reduceApp (state, action) { marketinfo: action.value.marketinfo, coinOptions: action.value.coinOptions, }, - buyAddress: appState.accountDetail.buyAddress, - amount: appState.accountDetail.amount, + buyAddress: appState.buyView.buyAddress, + amount: appState.buyView.amount, }, }) case actions.PAIR_UPDATE: return extend(appState, { - accountDetail: { + buyView: { subview: 'buyForm', formView: { coinbase: false, shapeshift: true, marketinfo: action.value.marketinfo, - coinOptions: appState.accountDetail.formView.coinOptions, + coinOptions: appState.buyView.formView.coinOptions, }, - buyAddress: appState.accountDetail.buyAddress, - amount: appState.accountDetail.amount, + buyAddress: appState.buyView.buyAddress, + amount: appState.buyView.amount, warning: null, }, }) - case actions.COIN_SHIFT_REQUEST: + case actions.SHOW_QR: return extend(appState, { - accountDetail: { - subview: 'buyForm', - formView: { - coinbase: false, - shapeshift: true, - marketinfo: appState.accountDetail.formView.marketinfo, - coinOptions: appState.accountDetail.formView.coinOptions, - response: action.value.response, - }, - buyAddress: appState.accountDetail.buyAddress, - amount: appState.accountDetail.amount, - warning: null, + qrRequested: true, + transForward: true, + Qr: { + message: action.value.message, + image: action.value.qr, + data: action.value.data, }, }) default: From b72c00a6c15af9692cbaf46b89a19e5cef45f7b7 Mon Sep 17 00:00:00 2001 From: Frankie Date: Fri, 12 Aug 2016 15:59:12 -0700 Subject: [PATCH 6/8] Add a to test faucet button --- ui/app/components/buy-button-subview.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js index 3f13878df..19a6e251f 100644 --- a/ui/app/components/buy-button-subview.js +++ b/ui/app/components/buy-button-subview.js @@ -96,7 +96,6 @@ BuyButtonSubview.prototype.formVersionSubview = function () { return h(ShapeshiftForm, this.props) } } else { - console.log(this.props.network) return h('div.flex-column', { style: { alignItems: 'center', @@ -108,6 +107,10 @@ BuyButtonSubview.prototype.formVersionSubview = function () { 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', { + onClick: () => this.props.dispatch(actions.buyEth()), + }, 'Go To Test Faucet') : null, ]) } } From 99a788a6f02ffcd53e88222ab0ba4b89d8040f4f Mon Sep 17 00:00:00 2001 From: Frankie Date: Fri, 12 Aug 2016 17:43:24 -0700 Subject: [PATCH 7/8] Add multi message capability to Qr view for market info --- ui/app/actions.js | 7 ++++--- ui/app/components/qr-code.js | 8 +++++++- ui/app/components/shapeshift-form.js | 8 ++++++-- ui/app/css/index.css | 10 ++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index b6b5e684b..61f900df9 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -692,13 +692,14 @@ function shapeShiftSubview (network) { } } -function coinShiftRquest (data) { +function coinShiftRquest (data, marketData) { return (dispatch) => { dispatch(actions.showLoadingIndication()) shapeShiftRequest('shift', { method: 'POST', data}, (response) => { if (response.error) return dispatch(actions.showWarning(response.error)) - var message = `Deposit your ${response.depositType} to the address bellow:` - dispatch(actions.getQr(response.deposit, '125x125', message)) + var message = ` + Deposit your ${response.depositType} to the address bellow:` + dispatch(actions.getQr(response.deposit, '125x125', [message].concat(marketData))) }) } } diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js index 765322239..1c744b234 100644 --- a/ui/app/components/qr-code.js +++ b/ui/app/components/qr-code.js @@ -29,7 +29,7 @@ QrCodeView.prototype.render = function () { alignItems: 'center', }, }, [ - h('h3', Qr.message), + Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('h3', Qr.message), h('#qr-container.flex-column', { key: 'qr', style: { @@ -48,3 +48,9 @@ QrCodeView.prototype.render = function () { ]), ]) } + +QrCodeView.prototype.renderMultiMessage = function () { + var Qr = this.props.Qr + var multiMessage = Qr.message.map((message) => h('.qr-message', message)) + return multiMessage +} diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index 52bacf798..48d220693 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -194,6 +194,7 @@ ShapeshiftForm.prototype.renderMain = function () { } 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 @@ -202,9 +203,12 @@ ShapeshiftForm.prototype.shift = function () { 'pair': pair, 'returnAddress': returnAddress, } - + 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)) + this.props.dispatch(actions.coinShiftRquest(data, message)) } } diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 3b385ad85..1278e95c9 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -582,3 +582,13 @@ input.large-input { font-family: "Montserrat Light"; margin-left: 5px; } + +.qr-message { + font-size: 12px; + color: #F7861C; +} + +div.message-container > div:first-child { + font-size: 15px; + color: #4D4D4D; +} From 31d6d5c50916a98c7cdc1cf898eeb9a046a60c72 Mon Sep 17 00:00:00 2001 From: Frankie Date: Sat, 13 Aug 2016 13:48:49 -0700 Subject: [PATCH 8/8] Add to change log --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9afea415c..c251be48e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog ## Current Master - +- Integrate ShapeShift +- Add a for for Coinbase to specify amount to buy - Fix various typos. - Make dapp-metamask connection more reliable