diff --git a/app/images/coinbase logo.png b/app/images/coinbase logo.png new file mode 100644 index 000000000..a23d7926d Binary files /dev/null and b/app/images/coinbase logo.png differ diff --git a/app/images/shapeshift logo.png b/app/images/shapeshift logo.png new file mode 100644 index 000000000..ac8faba5b Binary files /dev/null and b/app/images/shapeshift logo.png differ diff --git a/app/scripts/popup.js b/app/scripts/popup.js index d0952af6a..97a29fb1a 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -26,8 +26,13 @@ const container = document.getElementById('app-content') startPopup({ container, connectionStream }, (err, store) => { if (err) return displayCriticalError(err) - let betaUIState = store.getState().metamask.featureFlags.betaUI - let css = betaUIState ? NewMetaMaskUiCss() : OldMetaMaskUiCss() + const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask + const firstTime = Object.keys(identities).length === 0 + let betaUIState = featureFlags.betaUI + + const useBetaCss = isMascara || firstTime || betaUIState + + let css = useBetaCss ? NewMetaMaskUiCss() : OldMetaMaskUiCss() let deleteInjectedCss = injectCss(css) let newBetaUIState diff --git a/gulpfile.js b/gulpfile.js index 195e3f3ca..f61e15b69 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -23,6 +23,8 @@ var sass = require('gulp-sass') var autoprefixer = require('gulp-autoprefixer') var gulpStylelint = require('gulp-stylelint') var stylefmt = require('gulp-stylefmt') +var uglify = require('gulp-uglify') +var babel = require('gulp-babel') var disableDebugTools = gutil.env.disableDebugTools @@ -375,6 +377,11 @@ function bundleTask(opts) { // sourcemaps // loads map from browserify file .pipe(gulpif(debug, sourcemaps.init({ loadMaps: true }))) + // Minification + .pipe(babel({ + presets: ['env'] + })) + .pipe(uglify()) // writes .map file .pipe(gulpif(debug, sourcemaps.write('./'))) // write completed bundles diff --git a/mascara/server/index.js b/mascara/server/index.js index 83f84f6d1..6fb1287cc 100644 --- a/mascara/server/index.js +++ b/mascara/server/index.js @@ -2,6 +2,7 @@ const path = require('path') const express = require('express') const createBundle = require('./util').createBundle const serveBundle = require('./util').serveBundle +const compression = require('compression') module.exports = createMetamascaraServer @@ -16,6 +17,8 @@ function createMetamascaraServer () { // serve bundles const server = express() + server.use(compression()) + // ui window serveBundle(server, '/ui.js', uiBundle) server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') })) diff --git a/mascara/server/util.js b/mascara/server/util.js index af2daddb9..f9692afb6 100644 --- a/mascara/server/util.js +++ b/mascara/server/util.js @@ -23,7 +23,9 @@ function createBundle (entryPoint) { cache: {}, packageCache: {}, plugin: [watchify], - }).transform('babelify') + }) + .transform('babelify') + .transform('uglifyify', { global: true }) bundler.on('update', bundle) bundle() diff --git a/old-ui/app/account-detail.js b/old-ui/app/account-detail.js index ee7eb1258..af189cc79 100644 --- a/old-ui/app/account-detail.js +++ b/old-ui/app/account-detail.js @@ -162,6 +162,7 @@ AccountDetailScreen.prototype.render = function () { textRendering: 'geometricPrecision', marginTop: '15px', marginBottom: '15px', + marginLeft: '15px', color: '#AEAEAE', }, }, checksumAddress), diff --git a/old-ui/app/css/index.css b/old-ui/app/css/index.css index 3cbf20e98..4363da049 100644 --- a/old-ui/app/css/index.css +++ b/old-ui/app/css/index.css @@ -442,10 +442,10 @@ input.large-input { flex-wrap: wrap; overflow-y: auto; flex-direction: inherit; +} - .name-label { - margin-left: 15px; - } +.account-detail-section .name-label { + margin-left: 15px; } .grow-tenx { diff --git a/package.json b/package.json index 4469263cc..9bbdacea2 100644 --- a/package.json +++ b/package.json @@ -199,6 +199,7 @@ "eth-json-rpc-middleware": "^1.2.7", "fs-promise": "^2.0.3", "gulp": "github:gulpjs/gulp#4.0", + "gulp-babel": "^7.0.0", "gulp-if": "^2.0.1", "gulp-json-editor": "^2.2.1", "gulp-livereload": "^3.8.1", @@ -206,6 +207,7 @@ "gulp-sourcemaps": "^2.6.0", "gulp-stylefmt": "^1.1.0", "gulp-stylelint": "^4.0.0", + "gulp-uglify": "^3.0.0", "gulp-util": "^3.0.7", "gulp-watch": "^4.3.5", "gulp-zip": "^4.0.0", diff --git a/ui/app/actions.js b/ui/app/actions.js index bd3aab45a..0d96d2b59 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -925,9 +925,13 @@ function lockMetamask () { }) .then(newState => { dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.hideLoadingIndication()) + dispatch({ type: actions.LOCK_METAMASK }) + }) + .catch(() => { + dispatch(actions.hideLoadingIndication()) dispatch({ type: actions.LOCK_METAMASK }) }) - .catch(() => dispatch({ type: actions.LOCK_METAMASK })) } } @@ -1434,7 +1438,6 @@ function pairUpdate (coin) { function shapeShiftSubview (network) { var pair = 'btc_eth' - return (dispatch) => { dispatch(actions.showSubLoadingIndication()) shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { @@ -1460,7 +1463,7 @@ function coinShiftRquest (data, marketData) { dispatch(actions.hideLoadingIndication()) if (response.error) return dispatch(actions.displayWarning(response.error)) var message = ` - Deposit your ${response.depositType} to the address bellow:` + Deposit your ${response.depositType} to the address below:` log.debug(`background.createShapeShiftTx`) background.createShapeShiftTx(response.deposit, response.depositType) dispatch(actions.showQrView(response.deposit, [message].concat(marketData))) @@ -1496,7 +1499,7 @@ function reshowQrCode (data, coin) { if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) var message = [ - `Deposit your ${coin} to the address bellow:`, + `Deposit your ${coin} to the address below:`, `Deposit Limit: ${mktResponse.limit}`, `Deposit Minimum:${mktResponse.minimum}`, ] diff --git a/ui/app/app.js b/ui/app/app.js index 73e5c06db..e24ab7109 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -3,6 +3,8 @@ const Component = require('react').Component const connect = require('react-redux').connect const h = require('react-hyperscript') const actions = require('./actions') +const classnames = require('classnames') + // mascara const MascaraFirstTime = require('../../mascara/src/app/first-time').default const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default @@ -243,7 +245,9 @@ App.prototype.renderAppBar = function () { }, [ h('.app-header.flex-row.flex-space-between', { - style: {}, + className: classnames({ + 'app-header--initialized': !isOnboarding, + }), }, [ h('div.app-header-contents', {}, [ h('div.left-menu-wrapper', { diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js index 286a3b587..1b62b42fb 100644 --- a/ui/app/components/account-menu/index.js +++ b/ui/app/components/account-menu/index.js @@ -33,7 +33,7 @@ function mapDispatchToProps (dispatch) { }, lockMetamask: () => { dispatch(actions.lockMetamask()) - dispatch(actions.displayWarning(null)) + dispatch(actions.hideWarning()) dispatch(actions.hideSidebar()) dispatch(actions.toggleAccountMenu()) }, diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js index d735983f9..74a7a847e 100644 --- a/ui/app/components/modals/buy-options-modal.js +++ b/ui/app/components/modals/buy-options-modal.js @@ -69,7 +69,7 @@ BuyOptions.prototype.render = function () { // h('div.buy-modal-content-option', {}, [ // h('div.buy-modal-content-option-title', {}, 'Shapeshift'), // h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'), - // ]), + // ]),, this.renderModalContentOption( 'Direct Deposit', diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js new file mode 100644 index 000000000..3e6d3fde1 --- /dev/null +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -0,0 +1,182 @@ +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits +const connect = require('react-redux').connect +const actions = require('../../actions') +const networkNames = require('../../../../app/scripts/config.js').networkNames +const ShapeshiftForm = require('../shapeshift-form') + +const DIRECT_DEPOSIT_ROW_TITLE = 'Directly Deposit Ether' +const DIRECT_DEPOSIT_ROW_TEXT = `If you already have some Ether, the quickest way to get Ether in +your new wallet by direct deposit.` +const COINBASE_ROW_TITLE = 'Buy on Coinbase' +const COINBASE_ROW_TEXT = `Coinbase is the world’s most popular way to buy and sell bitcoin, +ethereum, and litecoin.` +const SHAPESHIFT_ROW_TITLE = 'Deposit with ShapeShift' +const SHAPESHIFT_ROW_TEXT = `If you own other cryptocurrencies, you can trade and deposit Ether +directly into your MetaMask wallet. No Account Needed.` +const FAUCET_ROW_TITLE = 'Test Faucet' +const facuetRowText = networkName => `Get Ether from a faucet for the ${networkName}` + +function mapStateToProps (state) { + return { + network: state.metamask.network, + address: state.metamask.selectedAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + toCoinbase: (address) => { + dispatch(actions.buyEth({ network: '1', address, amount: 0 })) + }, + hideModal: () => { + dispatch(actions.hideModal()) + }, + showAccountDetailModal: () => { + dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) + }, + toFaucet: network => dispatch(actions.buyEth({ network })), + } +} + +inherits(DepositEtherModal, Component) +function DepositEtherModal () { + Component.call(this) + + this.state = { + buyingWithShapeshift: false, + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) + +DepositEtherModal.prototype.renderRow = function ({ + logo, + title, + text, + buttonLabel, + onButtonClick, + hide, + className, + hideButton, + hideTitle, + onBackClick, +}) { + if (hide) { + return null + } + + return h('div', { + className: className || 'deposit-ether-modal__buy-row', + }, [ + + h('div.deposit-ether-modal__buy-row__back', { + onClick: onBackClick, + }, [ + + h('i.fa.fa-arrow-left.cursor-pointer'), + + ]), + + h('div.deposit-ether-modal__buy-row__logo', [logo]), + + h('div.deposit-ether-modal__buy-row__description', [ + + !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), + + h('div.deposit-ether-modal__buy-row__description__text', [text]), + + ]), + + !hideButton && h('div.deposit-ether-modal__buy-row__button', [ + h('button.deposit-ether-modal__deposit-button', { + onClick: onButtonClick, + }, [buttonLabel]), + ]), + + ]) +} + +DepositEtherModal.prototype.render = function () { + const { network, toCoinbase, address, toFaucet } = this.props + const { buyingWithShapeshift } = this.state + + const isTestNetwork = ['3', '4', '42'].find(n => n === network) + const networkName = networkNames[network] + + return h('div.deposit-ether-modal', {}, [ + + h('div.deposit-ether-modal__header', [ + + h('div.deposit-ether-modal__header__title', ['Deposit Ether']), + + h('div.deposit-ether-modal__header__description', [ + 'To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet.', + ]), + + h('div.deposit-ether-modal__header__close', { + onClick: () => { + this.setState({ buyingWithShapeshift: false }) + this.props.hideModal() + }, + }), + + ]), + + h('div.deposit-ether-modal__buy-rows', [ + + this.renderRow({ + logo: h('img.deposit-ether-modal__buy-row__eth-logo', { src: '../../../images/eth_logo.svg' }), + title: DIRECT_DEPOSIT_ROW_TITLE, + text: DIRECT_DEPOSIT_ROW_TEXT, + buttonLabel: 'View Account', + onButtonClick: () => this.goToAccountDetailsModal(), + hide: buyingWithShapeshift, + }), + + this.renderRow({ + logo: h('i.fa.fa-tint.fa-2x'), + title: FAUCET_ROW_TITLE, + text: facuetRowText(networkName), + buttonLabel: 'Get Ether', + onButtonClick: () => toFaucet(network), + hide: !isTestNetwork || buyingWithShapeshift, + }), + + this.renderRow({ + logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', { + src: '../../../images/coinbase logo.png', + }), + title: COINBASE_ROW_TITLE, + text: COINBASE_ROW_TEXT, + buttonLabel: 'Continue to Coinbase', + onButtonClick: () => toCoinbase(address), + hide: isTestNetwork || buyingWithShapeshift, + }), + + this.renderRow({ + logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', { + src: '../../../images/shapeshift logo.png', + }), + title: SHAPESHIFT_ROW_TITLE, + text: SHAPESHIFT_ROW_TEXT, + buttonLabel: 'Buy with Shapeshift', + onButtonClick: () => this.setState({ buyingWithShapeshift: true }), + hide: isTestNetwork, + hideButton: buyingWithShapeshift, + hideTitle: buyingWithShapeshift, + onBackClick: () => this.setState({ buyingWithShapeshift: false }), + className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy', + }), + + buyingWithShapeshift && h(ShapeshiftForm), + + ]), + ]) +} + +DepositEtherModal.prototype.goToAccountDetailsModal = function () { + this.props.hideModal() + this.props.showAccountDetailModal() +} diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index 2ff6accaa..afb2a2175 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -9,6 +9,7 @@ const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-n // Modal Components const BuyOptions = require('./buy-options-modal') +const DepositEtherModal = require('./deposit-ether-modal') const AccountDetailsModal = require('./account-details-modal') const EditAccountNameModal = require('./edit-account-name-modal') const ExportPrivateKeyModal = require('./export-private-key-modal') @@ -73,6 +74,37 @@ const MODALS = { }, }, + DEPOSIT_ETHER: { + contents: [ + h(DepositEtherModal, {}, []), + ], + mobileModalStyle: { + width: '100%', + height: '100%', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + boxShadow: '0 0 7px 0 rgba(0,0,0,0.08)', + top: '0', + display: 'flex', + }, + laptopModalStyle: { + width: '900px', + maxWidth: '900px', + top: 'calc(10% + 10px)', + left: '0', + right: '0', + margin: '0 auto', + boxShadow: '0 0 6px 0 rgba(0,0,0,0.3)', + borderRadius: '8px', + transform: 'none', + }, + contentStyle: { + borderRadius: '8px', + }, + }, + EDIT_ACCOUNT_NAME: { contents: [ h(EditAccountNameModal, {}, []), diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js index c5993e3d3..2270b8236 100644 --- a/ui/app/components/shapeshift-form.js +++ b/ui/app/components/shapeshift-form.js @@ -1,308 +1,242 @@ -const PersistentForm = require('../../lib/persistent-form') const h = require('react-hyperscript') const inherits = require('util').inherits +const Component = require('react').Component const connect = require('react-redux').connect -const actions = require('../actions') -const Qr = require('./qr-code') -const isValidAddress = require('../util').isValidAddress -module.exports = connect(mapStateToProps)(ShapeshiftForm) +const classnames = require('classnames') +const { qrcode } = require('qrcode-npm') +const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions') +const { isValidAddress } = require('../util') +const SimpleDropdown = require('./dropdowns/simple-dropdown') function mapStateToProps (state) { + const { + coinOptions, + tokenExchangeRates, + selectedAddress, + } = state.metamask + return { - warning: state.appState.warning, - isSubLoading: state.appState.isSubLoading, - qrRequested: state.appState.qrRequested, + coinOptions, + tokenExchangeRates, + selectedAddress, } } -inherits(ShapeshiftForm, PersistentForm) +function mapDispatchToProps (dispatch) { + return { + shapeShiftSubview: () => dispatch(shapeShiftSubview()), + pairUpdate: coin => dispatch(pairUpdate(coin)), + buyWithShapeShift: data => dispatch(buyWithShapeShift(data)), + } +} +module.exports = connect(mapStateToProps, mapDispatchToProps)(ShapeshiftForm) + +inherits(ShapeshiftForm, Component) function ShapeshiftForm () { - PersistentForm.call(this) - this.persistentFormParentId = 'shapeshift-buy-form' + Component.call(this) + + this.state = { + depositCoin: 'btc', + refundAddress: '', + showQrCode: false, + depositAddress: '', + errorMessage: '', + isLoading: false, + bought: false, + } } -ShapeshiftForm.prototype.render = function () { - return this.props.qrRequested ? h(Qr, {key: 'qr'}) : this.renderMain() +ShapeshiftForm.prototype.componentWillMount = function () { + this.props.shapeShiftSubview() } -ShapeshiftForm.prototype.renderMain = function () { - const marketinfo = this.props.buyView.formView.marketinfo - const coinOptions = this.props.buyView.formView.coinOptions - var coin = marketinfo.pair.split('_')[0].toUpperCase() - - return h('.flex-column', { - style: { - position: 'relative', - padding: '25px', - paddingTop: '5px', - width: '90%', - minHeight: '215px', - alignItems: 'center', - overflowY: 'auto', - }, - }, [ - h('.flex-row', { - style: { - justifyContent: 'center', - alignItems: 'baseline', - height: '42px', - }, - }, [ - h('img', { - src: coinOptions[coin].image, - width: '25px', - height: '25px', - style: { - marginRight: '5px', - }, - }), - - h('.input-container', { - position: 'relative', - }, [ - h('input#fromCoin.buy-inputs.ex-coins', { - type: 'text', - list: 'coinList', - autoFocus: true, - dataset: { - persistentFormId: 'input-coin', - }, - style: { - boxSizing: 'border-box', - }, - onChange: this.handleLiveInput.bind(this), - defaultValue: 'BTC', - }), - - this.renderCoinList(), - - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'absolute', - }, - }), - ]), - - h('.icon-control', { - style: { - position: 'relative', - }, - }, [ - // Not visible on the screen, can't see it on master. - - // h('i.fa.fa-refresh.fa-4.orange', { - // style: { - // bottom: '5px', - // left: '5px', - // color: '#F7861C', - // }, - // onClick: this.updateCoin.bind(this), - // }), - h('i.fa.fa-chevron-right.fa-4.orange', { - style: { - position: 'absolute', - bottom: '35%', - left: '0%', - color: '#F7861C', - }, - onClick: this.updateCoin.bind(this), - }), - ]), - - 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', - }, - }), - ]), - - h('.flex-column', { - style: { - marginTop: '1%', - alignItems: 'flex-start', - }, - }, [ - this.props.warning ? - this.props.warning && - h('span.error.flex-center', { - style: { - textAlign: 'center', - width: '229px', - height: '82px', - }, - }, this.props.warning) - : this.renderInfo(), - - this.renderRefundAddressForCoin(coin), - ]), - - ]) +ShapeshiftForm.prototype.onCoinChange = function (e) { + const coin = e.target.value + this.setState({ + depositCoin: coin, + errorMessage: '', + }) + this.props.pairUpdate(coin) } -ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) { - return h(this.activeToggle('.input-container'), { - style: { - marginTop: '1%', - }, - }, [ +ShapeshiftForm.prototype.onBuyWithShapeShift = function () { + this.setState({ + isLoading: true, + showQrCode: true, + }) - h('div', `${coin} Address:`), - - h('input#fromCoinAddress.buy-inputs', { - type: 'text', - placeholder: `Your ${coin} Refund Address`, - dataset: { - persistentFormId: 'refund-address', - - }, - style: { - boxSizing: 'border-box', - width: '227px', - height: '30px', - padding: ' 5px ', - }, - }), - - h('i.fa.fa-pencil-square-o.edit-text', { - style: { - fontSize: '12px', - color: '#F7861C', - position: 'absolute', - }, - }), - h('div.flex-row', { - style: { - justifyContent: 'flex-start', - }, - }, [ - h('button', { - onClick: this.shift.bind(this), - style: { - marginTop: '1%', - }, - }, - 'Submit'), - ]), - ]) -} - -ShapeshiftForm.prototype.shift = function () { - var props = this.props - var withdrawal = this.props.buyView.buyAddress - var returnAddress = document.getElementById('fromCoinAddress').value - var pair = this.props.buyView.formView.marketinfo.pair - var data = { - 'withdrawal': withdrawal, - 'pair': pair, - 'returnAddress': returnAddress, + const { + buyWithShapeShift, + selectedAddress: withdrawal, + } = this.props + const { + refundAddress: returnAddress, + depositCoin, + } = this.state + const pair = `${depositCoin}_eth` + const data = { + withdrawal, + pair, + returnAddress, // Public api key 'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6', } - var message = [ - `Deposit Limit: ${props.buyView.formView.marketinfo.limit}`, - `Deposit Minimum:${props.buyView.formView.marketinfo.minimum}`, - ] + if (isValidAddress(withdrawal)) { - this.props.dispatch(actions.coinShiftRquest(data, message)) + buyWithShapeShift(data) + .then(d => this.setState({ + showQrCode: true, + depositAddress: d.deposit, + isLoading: false, + })) + .catch(() => this.setState({ + showQrCode: false, + errorMessage: 'Invalid Request', + isLoading: false, + })) } } -ShapeshiftForm.prototype.renderCoinList = function () { - var list = Object.keys(this.props.buyView.formView.coinOptions).map((item) => { - return h('option', { - value: item, - }, item) - }) +ShapeshiftForm.prototype.renderMetadata = function (label, value) { + return h('div', {className: 'shapeshift-form__metadata-wrapper'}, [ - return h('datalist#coinList', { - onClick: (event) => { - event.preventDefault() - }, - }, list) -} + h('div.shapeshift-form__metadata-label', {}, [ + h('span', `${label}:`), + ]), -ShapeshiftForm.prototype.updateCoin = function (event) { - event.preventDefault() - const props = this.props - var coinOptions = this.props.buyView.formView.coinOptions - var coin = document.getElementById('fromCoin').value + h('div.shapeshift-form__metadata-value', {}, [ + h('span', value), + ]), - if (!coinOptions[coin.toUpperCase()] || coin.toUpperCase() === 'ETH') { - var message = 'Not a valid coin' - return props.dispatch(actions.displayWarning(message)) - } else { - return props.dispatch(actions.pairUpdate(coin)) - } -} - -ShapeshiftForm.prototype.handleLiveInput = function () { - const props = this.props - var coinOptions = this.props.buyView.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.buyView.formView.marketinfo - const coinOptions = this.props.buyView.formView.coinOptions - var coin = marketinfo.pair.split('_')[0].toUpperCase() - - return h('span', { - style: { - }, - }, [ - h('h3.flex-row.text-transform-uppercase', { - style: { - color: '#868686', - paddingTop: '4px', - justifyContent: 'space-around', - textAlign: 'center', - fontSize: '17px', - }, - }, `Market Info for ${marketinfo.pair.replace('_', ' to ').toUpperCase()}:`), - h('.marketinfo', ['Status : ', `${coinOptions[coin].status}`]), - h('.marketinfo', ['Exchange Rate: ', `${marketinfo.rate}`]), - h('.marketinfo', ['Limit: ', `${marketinfo.limit}`]), - h('.marketinfo', ['Minimum : ', `${marketinfo.minimum}`]), ]) } -ShapeshiftForm.prototype.activeToggle = function (elementType) { - if (!this.props.buyView.formView.response || this.props.warning) return elementType - return `${elementType}.inactive` -} +ShapeshiftForm.prototype.renderMarketInfo = function () { + const { depositCoin } = this.state + const coinPair = `${depositCoin}_eth` + const { tokenExchangeRates } = this.props + const { + limit, + rate, + minimum, + } = tokenExchangeRates[coinPair] || {} + + return h('div.shapeshift-form__metadata', {}, [ + + this.renderMetadata('Status', limit ? 'Available' : 'Unavailable'), + this.renderMetadata('Limit', limit), + this.renderMetadata('Exchange Rate', rate), + this.renderMetadata('Minimum', minimum), -ShapeshiftForm.prototype.renderLoading = function () { - return h('span', { - style: { - position: 'absolute', - left: '70px', - bottom: '194px', - background: 'transparent', - width: '229px', - height: '82px', - display: 'flex', - justifyContent: 'center', - }, - }, [ - h('img', { - style: { - width: '60px', - }, - src: 'images/loading.svg', - }), ]) } + +ShapeshiftForm.prototype.renderQrCode = function () { + const { depositAddress, isLoading } = this.state + const qrImage = qrcode(4, 'M') + qrImage.addData(depositAddress) + qrImage.make() + + return h('div.shapeshift-form', {}, [ + + h('div.shapeshift-form__deposit-instruction', [ + 'Deposit your BTC to the address below:', + ]), + + h('div', depositAddress), + + h('div.shapeshift-form__qr-code', [ + isLoading + ? h('img', { + src: 'images/loading.svg', + style: { width: '60px'}, + }) + : h('div', { + dangerouslySetInnerHTML: { __html: qrImage.createTableTag(4) }, + }), + ]), + + this.renderMarketInfo(), + + ]) +} + + +ShapeshiftForm.prototype.render = function () { + const { coinOptions, btnClass } = this.props + const { depositCoin, errorMessage, showQrCode, depositAddress } = this.state + const coinPair = `${depositCoin}_eth` + const { tokenExchangeRates } = this.props + const token = tokenExchangeRates[coinPair] + + return h('div.shapeshift-form-wrapper', [ + showQrCode + ? this.renderQrCode() + : h('div.shapeshift-form', [ + h('div.shapeshift-form__selectors', [ + + h('div.shapeshift-form__selector', [ + + h('div.shapeshift-form__selector-label', 'Deposit'), + + h(SimpleDropdown, { + selectedOption: this.state.depositCoin, + onSelect: this.onCoinChange, + options: Object.entries(coinOptions).map(([coin]) => ({ + value: coin.toLowerCase(), + displayValue: coin, + })), + }), + + ]), + + h('div.icon.shapeshift-form__caret', { + style: { backgroundImage: 'url(images/caret-right.svg)'}, + }), + + h('div.shapeshift-form__selector', [ + + h('div.shapeshift-form__selector-label', [ + 'Receive', + ]), + + h('div.shapeshift-form__selector-input', ['ETH']), + + ]), + + ]), + + h('div', { + className: classnames('shapeshift-form__address-input-wrapper', { + 'shapeshift-form__address-input-wrapper--error': errorMessage, + }), + }, [ + + h('div.shapeshift-form__address-input-label', [ + 'Your Refund Address', + ]), + + h('input.shapeshift-form__address-input', { + type: 'text', + onChange: e => this.setState({ + refundAddress: e.target.value, + errorMessage: '', + }), + }), + + h('divshapeshift-form__address-input-error-message', [errorMessage]), + ]), + + this.renderMarketInfo(), + + ]), + + !depositAddress && h('button.shapeshift-form__shapeshift-buy-btn', { + className: btnClass, + disabled: !token, + onClick: () => this.onBuyWithShapeShift(), + }, ['Buy']), + + ]) +} diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js index 43973de63..111a77df4 100644 --- a/ui/app/components/shift-list-item.js +++ b/ui/app/components/shift-list-item.js @@ -16,6 +16,7 @@ module.exports = connect(mapStateToProps)(ShiftListItem) function mapStateToProps (state) { return { + selectedAddress: state.metamask.selectedAddress, conversionRate: state.metamask.conversionRate, currentCurrency: state.metamask.currentCurrency, } @@ -28,36 +29,39 @@ function ShiftListItem () { } ShiftListItem.prototype.render = function () { + const { selectedAddress, receivingAddress } = this.props return ( - h('div.tx-list-item.tx-list-clickable', { - style: { - paddingTop: '20px', - paddingBottom: '20px', - justifyContent: 'space-around', - alignItems: 'center', - }, - }, [ - h('div', { + selectedAddress === receivingAddress + ? h('div.tx-list-item.tx-list-clickable', { style: { - width: '0px', - position: 'relative', - bottom: '19px', + paddingTop: '20px', + paddingBottom: '20px', + justifyContent: 'space-around', + alignItems: 'center', }, }, [ - h('img', { - src: 'https://info.shapeshift.io/sites/default/files/logo.png', + h('div', { style: { - height: '35px', - width: '132px', - position: 'absolute', - clip: 'rect(0px,23px,34px,0px)', + width: '0px', + position: 'relative', + bottom: '19px', }, - }), - ]), + }, [ + h('img', { + src: 'https://info.shapeshift.io/sites/default/files/logo.png', + style: { + height: '35px', + width: '132px', + position: 'absolute', + clip: 'rect(0px,23px,34px,0px)', + }, + }), + ]), - this.renderInfo(), - this.renderUtilComponents(), - ]) + this.renderInfo(), + this.renderUtilComponents(), + ]) + : null ) } diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index 70722f43e..50e328dac 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -52,7 +52,7 @@ TxList.prototype.render = function () { TxList.prototype.renderTransaction = function () { const { txsToRender, conversionRate } = this.props return txsToRender.length - ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate)) + ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate, i)) : [h( 'div.tx-list-item.tx-list-item--empty', { key: 'tx-list-none' }, @@ -61,12 +61,16 @@ TxList.prototype.renderTransaction = function () { } // TODO: Consider moving TxListItem into a separate component -TxList.prototype.renderTransactionListItem = function (transaction, conversionRate) { +TxList.prototype.renderTransactionListItem = function (transaction, conversionRate, index) { // console.log({transaction}) // refer to transaction-list.js:line 58 if (transaction.key === 'shapeshift') { - return h(ShiftListItem, transaction) + return h('div', { + key: `shapeshift${index}`, + }, [ + h(ShiftListItem, transaction), + ]) } const props = { diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js index 72183f0f7..0148b32a5 100644 --- a/ui/app/components/tx-view.js +++ b/ui/app/components/tx-view.js @@ -70,7 +70,7 @@ TxView.prototype.renderButtons = function () { h('div.flex-row.flex-center.hero-balance-buttons', [ h('button.btn-clear.hero-balance-button', { onClick: () => showModal({ - name: 'BUY', + name: 'DEPOSIT_ETHER', }), }, 'DEPOSIT'), diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss index 2a1f7abc7..ac2cecf7e 100644 --- a/ui/app/css/itcss/components/header.scss +++ b/ui/app/css/itcss/components/header.scss @@ -17,7 +17,16 @@ @media screen and (min-width: 576px) { height: 75px; justify-content: center; + } + .metafox-icon { + cursor: pointer; + } +} + +.app-header--initialized { + + @media screen and (min-width: 576px) { &::after { content: ''; position: absolute; @@ -27,10 +36,6 @@ bottom: -32px; } } - - .metafox-icon { - cursor: pointer; - } } .app-header-contents { diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss index 307401666..7a2ec35a4 100644 --- a/ui/app/css/itcss/components/modal.scss +++ b/ui/app/css/itcss/components/modal.scss @@ -589,4 +589,256 @@ justify-content: center; font-size: 17px; color: $nile-blue; -} \ No newline at end of file +} + +// Deposit Ether Modal +.deposit-ether-modal { + border-radius: 8px; + font-family: Roboto; + display: flex; + flex-flow: column; + height: 100%; + + &__header { + width: 100%; + border-radius: 8px 8px 0 0; + background-color: $mid-gray; + display: flex; + position: relative; + padding: 25px; + flex-flow: column; + align-items: flex-start; + + &__title { + color: $white; + font-size: 24px; + line-height: 32px; + } + + &__description { + color: $white; + font-size: 16px; + line-height: 22px; + margin-top: 10px; + } + + &__close::after { + content: '\00D7'; + font-size: 2em; + color: $white; + position: absolute; + top: 20.8px; + right: 28px; + cursor: pointer; + } + } + + &__buy-rows { + width: 100%; + padding: 33px; + padding-top: 0px; + display: flex; + flex-flow: column nowrap; + flex: 1; + overflow-y: auto; + + @media screen and (max-width: 575px) { + height: 0; + } + } + + &__buy-row { + border-bottom: 1px solid $alto; + display: flex; + justify-content: space-between; + align-items: center; + flex: 1; + padding-bottom: 25px; + padding-top: 25px; + + @media screen and (max-width: 575px) { + min-height: 360px; + flex-flow: column; + justify-content: center; + padding-top: 45px; + } + + &__back { + position: absolute; + top: 10px; + left: 0px; + } + + &__shapeshift-buy { + padding-top: 25px; + position: relative; + @media screen and (max-width: 575px) { + display: flex; + justify-content: space-between; + align-items: center; + flex: 1; + padding-bottom: 25px; + flex-flow: column; + justify-content: center; + padding-top: 20px; + min-height: 240px; + border: none; + } + } + + &__logo { + display: flex; + justify-content: center; + flex: 0.3 1 auto; + + @media screen and (min-width: 575px) { + min-width: 215px; + } + } + + &__coinbase-logo { + height: 40px; + width: 180px; + } + + &__shapeshift-logo { + height: 60px; + width: 174px; + } + + &__eth-logo { + border-radius: 50%; + width: 68px; + height: 68px; + border: 3px solid $tundora; + z-index: 25; + padding: 4px; + background-color: #fff; + } + + &__right { + display: flex; + } + + &__description { + color: $cape-cod; + flex: 0.5 1 auto; + + @media screen and (min-width: 575px) { + min-width: 315px; + } + + &__title { + font-size: 20px; + line-height: 30px; + } + + &__text { + font-size: 14px; + line-height: 22px; + margin-top: 7px; + } + } + + &__button { + display: flex; + justify-content: flex-end; + + @media screen and (min-width: 575px) { + min-width: 300px; + } + } + } + + &__buy-row:last-of-type { + border-bottom: 0px; + } + + &__deposit-button, .shapeshift-form__shapeshift-buy-btn { + height: 54px; + width: 257px; + border: 1px solid $curious-blue; + border-radius: 4px; + display: flex; + justify-content: center; + font-size: 16px; + color: $curious-blue; + background-color: $white; + } + + .shapeshift-form-wrapper { + display: flex; + flex-flow: column; + justify-content: center; + align-items: center; + margin-top: 28px; + flex: 1 0 auto; + + .shapeshift-form { + width: auto; + + &__caret { + width: auto; + flex: 1; + } + } + } + + .shapeshift-form__shapeshift-buy-btn { + margin-top: 10px; + } + + .simple-dropdown { + color: #5B5D67; + font-size: 16px; + font-weight: 300; + line-height: 21px; + border: 1px solid #D8D8D8; + background-color: #FFFFFF; + text-align: center; + width: 100%; + height: 45px; + line-height: 44px; + font-family: Montserrat Light; + } + + .simple-dropdown__selected { + text-align: center; + } +} + +//Notification Modal + +.notification-modal-wrapper { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + position: relative; + border: 1px solid $alto; + box-shadow: 0 0 2px 2px $alto; + font-family: Roboto; +} + +.notification-modal-header { + background: $wild-sand; + width: 100%; + display: flex; + justify-content: center; + padding: 30px; + font-size: 22px; + color: $nile-blue; + height: 79px; +} + +.notification-modal-message { + padding: 20px; +} + +.notification-modal-message { + width: 100%; + display: flex; + justify-content: center; + font-size: 17px; + color: $nile-blue; +} diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss index 0ff0b3dda..19dadc69a 100644 --- a/ui/app/css/itcss/components/transaction-list.scss +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -206,7 +206,7 @@ } @media screen and (min-width: $break-large) { - margin: 0 2.37em; + padding: 0 2.37em; } &:last-of-type { diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index 8bd1ad20d..4c0972527 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -44,6 +44,8 @@ $jaffa: #f28930; $geyser: #d2d8dd; $manatee: #93949d; $spindle: #c7ddec; +$mid-gray: #5b5d67; +$cape-cod: #38393a; /* Z-Indicies diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 3a4fb536d..e96dea0be 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -58,6 +58,7 @@ function reduceApp (state, action) { isLoading: false, // Used to display error text warning: null, + buyView: {}, }, state.appState) switch (action.type) { @@ -591,8 +592,8 @@ function reduceApp (state, action) { marketinfo: action.value.marketinfo, coinOptions: action.value.coinOptions, }, - buyAddress: appState.buyView.buyAddress, - amount: appState.buyView.amount, + buyAddress: action.value.buyAddress || appState.buyView.buyAddress, + amount: appState.buyView.amount || 0, }, })