aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorChi Kei Chan <chikeichan@gmail.com>2017-10-26 10:46:42 +0800
committerChi Kei Chan <chikeichan@gmail.com>2017-10-26 10:46:42 +0800
commitdb5326dfc288015a4d9f792653a71fd4d7abca33 (patch)
treecb93f52f999816d07a6146274093aaebdb7ed8d3 /ui
parenta9a841ba01f6bffa29a3e0491e3a88f6aff72ac7 (diff)
parentb2e440e4ffba6db29cf8a928a41534c1f204d485 (diff)
downloadtangerine-wallet-browser-db5326dfc288015a4d9f792653a71fd4d7abca33.tar.gz
tangerine-wallet-browser-db5326dfc288015a4d9f792653a71fd4d7abca33.tar.zst
tangerine-wallet-browser-db5326dfc288015a4d9f792653a71fd4d7abca33.zip
Merge branch 'NewUI-flat' into uat
Diffstat (limited to 'ui')
-rw-r--r--ui/app/accounts/import/json.js2
-rw-r--r--ui/app/actions.js187
-rw-r--r--ui/app/add-token.js11
-rw-r--r--ui/app/app.js115
-rw-r--r--ui/app/components/account-dropdowns.js7
-rw-r--r--ui/app/components/account-menu/index.js20
-rw-r--r--ui/app/components/bn-as-decimal-input.js16
-rw-r--r--ui/app/components/buy-button-subview.js2
-rw-r--r--ui/app/components/customize-gas-modal/index.js162
-rw-r--r--ui/app/components/dropdowns/account-dropdown-mini.js78
-rw-r--r--ui/app/components/dropdowns/components/dropdown.js4
-rw-r--r--ui/app/components/dropdowns/simple-dropdown.js91
-rw-r--r--ui/app/components/editable-label.js114
-rw-r--r--ui/app/components/ens-input.js2
-rw-r--r--ui/app/components/identicon.js2
-rw-r--r--ui/app/components/menu-droppo.js2
-rw-r--r--ui/app/components/modals/account-details-modal.js21
-rw-r--r--ui/app/components/modals/buy-options-modal.js4
-rw-r--r--ui/app/components/modals/export-private-key-modal.js5
-rw-r--r--ui/app/components/modals/modal.js7
-rw-r--r--ui/app/components/modals/new-account-modal.js16
-rw-r--r--ui/app/components/network.js7
-rw-r--r--ui/app/components/notice.js2
-rw-r--r--ui/app/components/pending-personal-msg.js47
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js4
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js4
-rw-r--r--ui/app/components/qr-code.js10
-rw-r--r--ui/app/components/send/account-list-item.js8
-rw-r--r--ui/app/components/send/currency-display.js42
-rw-r--r--ui/app/components/send/send-constants.js20
-rw-r--r--ui/app/components/send/send-utils.js39
-rw-r--r--ui/app/components/send/send-v2-container.js9
-rw-r--r--ui/app/components/send/to-autocomplete.js118
-rw-r--r--ui/app/components/shapeshift-form.js4
-rw-r--r--ui/app/components/signature-request.js255
-rw-r--r--ui/app/components/tab-bar.js63
-rw-r--r--ui/app/components/tx-view.js9
-rw-r--r--ui/app/components/typed-message-renderer.js4
-rw-r--r--ui/app/components/wallet-view.js112
-rw-r--r--ui/app/conf-tx.js33
-rw-r--r--ui/app/config.js215
-rw-r--r--ui/app/css/itcss/components/account-dropdown-mini.scss48
-rw-r--r--ui/app/css/itcss/components/account-dropdown.scss7
-rw-r--r--ui/app/css/itcss/components/account-menu.scss14
-rw-r--r--ui/app/css/itcss/components/add-token.scss87
-rw-r--r--ui/app/css/itcss/components/confirm.scss29
-rw-r--r--ui/app/css/itcss/components/currency-display.scss10
-rw-r--r--ui/app/css/itcss/components/editable-label.scss34
-rw-r--r--ui/app/css/itcss/components/header.scss7
-rw-r--r--ui/app/css/itcss/components/index.scss11
-rw-r--r--ui/app/css/itcss/components/modal.scss7
-rw-r--r--ui/app/css/itcss/components/network.scss15
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss103
-rw-r--r--ui/app/css/itcss/components/request-signature.scss222
-rw-r--r--ui/app/css/itcss/components/sections.scss4
-rw-r--r--ui/app/css/itcss/components/send.scss89
-rw-r--r--ui/app/css/itcss/components/settings.scss201
-rw-r--r--ui/app/css/itcss/components/simple-dropdown.scss65
-rw-r--r--ui/app/css/itcss/components/tab-bar.scss23
-rw-r--r--ui/app/css/itcss/components/token-list.scss6
-rw-r--r--ui/app/css/itcss/components/wallet-balance.scss3
-rw-r--r--ui/app/css/itcss/tools/utilities.scss1
-rw-r--r--ui/app/keychains/hd/create-vault-complete.js9
-rw-r--r--ui/app/main-container.js4
-rw-r--r--ui/app/reducers.js4
-rw-r--r--ui/app/reducers/app.js10
-rw-r--r--ui/app/reducers/metamask.js42
-rw-r--r--ui/app/selectors.js23
-rw-r--r--ui/app/send-v2.js94
-rw-r--r--ui/app/settings.js378
-rw-r--r--ui/css.js1
-rw-r--r--ui/lib/tx-helper.js2
72 files changed, 2643 insertions, 783 deletions
diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js
index 158a3c923..486ed8886 100644
--- a/ui/app/accounts/import/json.js
+++ b/ui/app/accounts/import/json.js
@@ -5,7 +5,7 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const FileInput = require('react-simple-file-input').default
-const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file'
+const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
module.exports = connect(mapStateToProps)(JsonImportSubview)
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 4c83f95b4..2e9b34c58 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -1,5 +1,6 @@
const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
+const ethUtil = require('ethereumjs-util')
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
@@ -143,6 +144,7 @@ var actions = {
UPDATE_SEND_AMOUNT: 'UPDATE_SEND_AMOUNT',
UPDATE_SEND_MEMO: 'UPDATE_SEND_MEMO',
UPDATE_SEND_ERRORS: 'UPDATE_SEND_ERRORS',
+ CLEAR_SEND: 'CLEAR_SEND',
updateGasLimit,
updateGasPrice,
updateGasTotal,
@@ -151,6 +153,7 @@ var actions = {
updateSendAmount,
updateSendMemo,
updateSendErrors,
+ clearSend,
setSelectedAddress,
// app messages
confirmSeedWords: confirmSeedWords,
@@ -181,9 +184,12 @@ var actions = {
showLoadingIndication: showLoadingIndication,
hideLoadingIndication: hideLoadingIndication,
// buy Eth with coinbase
+ onboardingBuyEthView,
+ ONBOARDING_BUY_ETH_VIEW: 'ONBOARDING_BUY_ETH_VIEW',
BUY_ETH: 'BUY_ETH',
buyEth: buyEth,
buyEthView: buyEthView,
+ buyWithShapeShift,
BUY_ETH_VIEW: 'BUY_ETH_VIEW',
COINBASE_SUBVIEW: 'COINBASE_SUBVIEW',
coinBaseSubview: coinBaseSubview,
@@ -268,14 +274,18 @@ function confirmSeedWords () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.clearSeedWordCache`)
- background.clearSeedWordCache((err, account) => {
- dispatch(actions.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
+ return new Promise((resolve, reject) => {
+ background.clearSeedWordCache((err, account) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ reject(err)
+ }
- log.info('Seed word cache cleared. ' + account)
- dispatch(actions.showAccountDetail(account))
+ log.info('Seed word cache cleared. ' + account)
+ dispatch(actions.showAccountsPage())
+ resolve(account)
+ })
})
}
}
@@ -284,10 +294,20 @@ function createNewVaultAndRestore (password, seed) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.createNewVaultAndRestore`)
- background.createNewVaultAndRestore(password, seed, (err) => {
- dispatch(actions.hideLoadingIndication())
- if (err) return dispatch(actions.displayWarning(err.message))
- dispatch(actions.showAccountsPage())
+
+ return new Promise((resolve, reject) => {
+ background.createNewVaultAndRestore(password, seed, (err) => {
+
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.showAccountsPage())
+ resolve()
+ })
})
}
}
@@ -296,19 +316,26 @@ function createNewVaultAndKeychain (password) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.createNewVaultAndKeychain`)
- background.createNewVaultAndKeychain(password, (err) => {
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
- log.debug(`background.placeSeedWords`)
- background.placeSeedWords((err) => {
+
+ return new Promise((resolve, reject) => {
+ background.createNewVaultAndKeychain(password, (err) => {
if (err) {
- return dispatch(actions.displayWarning(err.message))
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
}
- dispatch(actions.hideLoadingIndication())
- forceUpdateMetamaskState(dispatch)
+ log.debug(`background.placeSeedWords`)
+ background.placeSeedWords((err) => {
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+ dispatch(actions.hideLoadingIndication())
+ forceUpdateMetamaskState(dispatch)
+ resolve()
+ })
})
})
+
}
}
@@ -352,18 +379,25 @@ function importNewAccount (strategy, args) {
return (dispatch) => {
dispatch(actions.showLoadingIndication('This may take a while, be patient.'))
log.debug(`background.importAccountWithStrategy`)
- background.importAccountWithStrategy(strategy, args, (err) => {
- if (err) return dispatch(actions.displayWarning(err.message))
- log.debug(`background.getState`)
- background.getState((err, newState) => {
- dispatch(actions.hideLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.importAccountWithStrategy(strategy, args, (err) => {
if (err) {
- return dispatch(actions.displayWarning(err.message))
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
}
- dispatch(actions.updateMetamaskState(newState))
- dispatch({
- type: actions.SHOW_ACCOUNT_DETAIL,
- value: newState.selectedAddress,
+ log.debug(`background.getState`)
+ background.getState((err, newState) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+ dispatch(actions.updateMetamaskState(newState))
+ dispatch({
+ type: actions.SHOW_ACCOUNT_DETAIL,
+ value: newState.selectedAddress,
+ })
+ resolve(newState)
})
})
})
@@ -577,13 +611,18 @@ function updateSendMemo (memo) {
}
function updateSendErrors (error) {
- console.log(`updateSendErrors error`, error);
return {
type: actions.UPDATE_SEND_ERRORS,
value: error,
}
}
+function clearSend () {
+ return {
+ type: actions.CLEAR_SEND
+ }
+}
+
function sendTx (txData) {
log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
@@ -603,7 +642,7 @@ function signTokenTx (tokenAddress, toAddress, amount, txData) {
return dispatch => {
dispatch(actions.showLoadingIndication())
const token = global.eth.contract(abi).at(tokenAddress)
- token.transfer(toAddress, amount, txData)
+ token.transfer(toAddress, ethUtil.addHexPrefix(amount), txData)
.catch(err => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.displayWarning(err.message))
@@ -941,21 +980,23 @@ function goBackToInitView () {
function markNoticeRead (notice) {
return (dispatch) => {
- dispatch(this.showLoadingIndication())
+ dispatch(actions.showLoadingIndication())
log.debug(`background.markNoticeRead`)
- background.markNoticeRead(notice, (err, notice) => {
- dispatch(this.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err))
- }
- if (notice) {
- return dispatch(actions.showNotice(notice))
- } else {
- dispatch(this.clearNotices())
- return {
- type: actions.SHOW_ACCOUNTS_PAGE,
+ return new Promise((resolve, reject) => {
+ background.markNoticeRead(notice, (err, notice) => {
+ dispatch(actions.hideLoadingIndication())
+ if (err) {
+ dispatch(actions.displayWarning(err))
+ return reject(err)
}
- }
+ if (notice) {
+ dispatch(actions.showNotice(notice))
+ resolve()
+ } else {
+ dispatch(actions.clearNotices())
+ resolve()
+ }
+ })
})
}
}
@@ -993,7 +1034,7 @@ function setProviderType (type) {
dispatch(actions.updateProviderType(type))
dispatch(actions.setSelectedToken())
})
-
+
}
}
@@ -1172,14 +1213,22 @@ function saveAccountLabel (account, label) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
log.debug(`background.saveAccountLabel`)
- background.saveAccountLabel(account, label, (err) => {
- dispatch(actions.hideLoadingIndication())
- if (err) {
- return dispatch(actions.displayWarning(err.message))
- }
- dispatch({
- type: actions.SAVE_ACCOUNT_LABEL,
- value: { account, label },
+
+ return new Promise((resolve, reject) => {
+ background.saveAccountLabel(account, label, (err) => {
+ dispatch(actions.hideLoadingIndication())
+
+ if (err) {
+ dispatch(actions.displayWarning(err.message))
+ reject(err)
+ }
+
+ dispatch({
+ type: actions.SAVE_ACCOUNT_LABEL,
+ value: { account, label },
+ })
+
+ resolve(account)
})
})
}
@@ -1207,6 +1256,13 @@ function buyEth (opts) {
}
}
+function onboardingBuyEthView (address) {
+ return {
+ type: actions.ONBOARDING_BUY_ETH_VIEW,
+ value: address,
+ }
+}
+
function buyEthView (address) {
return {
type: actions.BUY_ETH_VIEW,
@@ -1272,6 +1328,18 @@ function coinShiftRquest (data, marketData) {
}
}
+function buyWithShapeShift (data) {
+ return dispatch => new Promise((resolve, reject) => {
+ shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
+ if (response.error) {
+ return reject(response.error)
+ }
+ background.createShapeShiftTx(response.deposit, response.depositType)
+ return resolve(response)
+ })
+ })
+}
+
function showQrView (data, message) {
return {
type: actions.SHOW_QR_VIEW,
@@ -1308,9 +1376,14 @@ function shapeShiftRequest (query, options, cb) {
options.method ? method = options.method : method = 'GET'
var requestListner = function (request) {
- queryResponse = JSON.parse(this.responseText)
- cb ? cb(queryResponse) : null
- return queryResponse
+ try {
+ queryResponse = JSON.parse(this.responseText)
+ cb ? cb(queryResponse) : null
+ return queryResponse
+ } catch (e) {
+ cb ? cb({error: e}) : null
+ return e
+ }
}
var shapShiftReq = new XMLHttpRequest()
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index e313babf3..518701a1d 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -255,9 +255,9 @@ AddTokenScreen.prototype.renderTokenList = function () {
h('div.add-token__token-symbol', symbol),
h('div.add-token__token-name', name),
]),
- tokenAlreadyAdded && (
- h('div.add-token__token-message', 'Already added')
- ),
+ // tokenAlreadyAdded && (
+ // h('div.add-token__token-message', 'Already added')
+ // ),
])
)
})
@@ -350,7 +350,10 @@ AddTokenScreen.prototype.render = function () {
h('div.add-token__footers', [
h('div.add-token__add-custom', {
onClick: () => this.setState({ isCollapsed: !isCollapsed }),
- }, 'Add custom token'),
+ }, [
+ 'Add custom token',
+ h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`),
+ ]),
this.renderCustomForm(),
]),
]),
diff --git a/ui/app/app.js b/ui/app/app.js
index ae38fad7f..7264c79c7 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -4,6 +4,9 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const { checkFeatureToggle } = require('../lib/feature-toggle-utils')
const actions = require('./actions')
+// mascara
+const MascaraFirstTime = require('../../mascara/src/app/first-time').default
+const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
// init
const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain')
@@ -21,7 +24,7 @@ const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
const WalletView = require('./components/wallet-view')
// other views
-const ConfigScreen = require('./config')
+const Settings = require('./settings')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const InfoScreen = require('./info')
@@ -50,6 +53,9 @@ function mapStateToProps (state) {
accounts,
address,
keyrings,
+ isInitialized,
+ noActiveNotices,
+ seedWords,
} = state.metamask
const selected = address || Object.keys(accounts)[0]
@@ -66,6 +72,8 @@ function mapStateToProps (state) {
currentView: state.appState.currentView,
activeAddress: state.appState.activeAddress,
transForward: state.appState.transForward,
+ isMascara: state.metamask.isMascara,
+ isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
seedWords: state.metamask.seedWords,
unapprovedTxs: state.metamask.unapprovedTxs,
unapprovedMsgs: state.metamask.unapprovedMsgs,
@@ -140,6 +148,8 @@ App.prototype.render = function () {
(isLoading || isLoadingNetwork) && h(Loading, {
loadingMessage: loadMessage,
}),
+
+ // this.renderLoadingIndicator({ isLoading, isLoadingNetwork, loadMessage }),
// content
this.renderPrimary(),
@@ -203,9 +213,35 @@ App.prototype.renderSidebar = function () {
}
App.prototype.renderAppBar = function () {
+ const {
+ isUnlocked,
+ network,
+ provider,
+ networkDropdownOpen,
+ showNetworkDropdown,
+ hideNetworkDropdown,
+ currentView,
+ } = this.props
+
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
+
+ const props = this.props
+ const state = this.state || {}
+ const isNetworkMenuOpen = state.isNetworkMenuOpen || false
+ const {isMascara, isOnboarding} = props
+
+ // Do not render header if user is in mascara onboarding
+ if (isMascara && isOnboarding) {
+ return null
+ }
+
+ // Do not render header if user is in mascara buy ether
+ if (isMascara && props.currentView.name === 'buyEth') {
+ return null
+ }
+
return (
h('.full-width', {
@@ -217,12 +253,14 @@ App.prototype.renderAppBar = function () {
}, [
h('div.app-header-contents', {}, [
h('div.left-menu-wrapper', {
- style: {},
+ onClick: () => {
+ props.dispatch(actions.backToAccountDetail(props.activeAddress))
+ },
}, [
// mini logo
- h('img', {
- height: 24,
- width: 24,
+ h('img.metafox-icon', {
+ height: 29,
+ width: 29,
src: '/images/icon-128.png',
}),
@@ -243,22 +281,21 @@ App.prototype.renderAppBar = function () {
}, [
// Network Indicator
h(NetworkIndicator, {
- network: this.props.network,
- provider: this.props.provider,
+ network,
+ provider,
+ disabled: currentView.name === 'confTx',
onClick: (event) => {
event.preventDefault()
event.stopPropagation()
- if (this.props.networkDropdownOpen === false) {
- this.props.showNetworkDropdown()
- } else {
- this.props.hideNetworkDropdown()
- }
+ return networkDropdownOpen === false
+ ? showNetworkDropdown()
+ : hideNetworkDropdown()
},
}),
]),
- h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
+ isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
h(Identicon, {
address: this.props.selectedAddress,
diameter: 32,
@@ -273,6 +310,17 @@ App.prototype.renderAppBar = function () {
}
+App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
+ const { isMascara } = this.props
+
+ return isMascara
+ ? null
+ : h(Loading, {
+ isLoading: isLoading || isLoadingNetwork,
+ loadingMessage: loadMessage,
+ })
+}
+
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
@@ -295,6 +343,11 @@ App.prototype.renderBackButton = function (style, justArrow = false) {
App.prototype.renderPrimary = function () {
log.debug('rendering primary')
var props = this.props
+ const {isMascara, isOnboarding} = props
+
+ if (isMascara && isOnboarding) {
+ return h(MascaraFirstTime)
+ }
// notices
if (!props.noActiveNotices) {
@@ -383,7 +436,7 @@ App.prototype.renderPrimary = function () {
case 'config':
log.debug('rendering config screen')
- return h(ConfigScreen, {key: 'config'})
+ return h(Settings, {key: 'config'})
case 'import-menu':
log.debug('rendering import screen')
@@ -395,12 +448,44 @@ App.prototype.renderPrimary = function () {
case 'info':
log.debug('rendering info screen')
- return h(InfoScreen, {key: 'info'})
+ return h(Settings, {key: 'info', tab: 'info'})
case 'buyEth':
log.debug('rendering buy ether screen')
return h(BuyView, {key: 'buyEthView'})
+ case 'onboardingBuyEth':
+ log.debug('rendering onboarding buy ether screen')
+ return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
+
+ case 'qr':
+ log.debug('rendering show qr screen')
+ return h('div', {
+ style: {
+ position: 'absolute',
+ height: '100%',
+ top: '0px',
+ left: '0px',
+ },
+ }, [
+ h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
+ onClick: () => props.dispatch(actions.backToAccountDetail(props.activeAddress)),
+ style: {
+ marginLeft: '10px',
+ marginTop: '50px',
+ },
+ }),
+ h('div', {
+ style: {
+ position: 'absolute',
+ left: '44px',
+ width: '285px',
+ },
+ }, [
+ h(QrView, {key: 'qr'}),
+ ]),
+ ])
+
default:
log.debug('rendering default, account detail screen')
return h(MainContainer, {key: 'account-detail'})
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
index 1b46e532a..0c34a5154 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/account-dropdowns.js
@@ -161,8 +161,6 @@ class AccountDropdowns extends Component {
)
}
-
-
renderAccountOptions () {
const { actions } = this.props
const { optionsMenuActive } = this.state
@@ -297,6 +295,11 @@ AccountDropdowns.propTypes = {
identities: PropTypes.objectOf(PropTypes.object),
selected: PropTypes.string,
keyrings: PropTypes.array,
+ actions: PropTypes.objectOf(PropTypes.func),
+ network: PropTypes.string,
+ style: PropTypes.object,
+ enableAccountOptions: PropTypes.bool,
+ enableAccountsSelector: PropTypes.bool,
}
const mapDispatchToProps = (dispatch) => {
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index 85bd21076..38c7bcb2d 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -46,6 +46,10 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.showImportPage())
dispatch(actions.toggleAccountMenu())
},
+ showInfoPage: () => {
+ dispatch(actions.showInfoPage())
+ dispatch(actions.toggleAccountMenu())
+ },
}
}
@@ -57,16 +61,18 @@ AccountMenu.prototype.render = function () {
showImportPage,
lockMetamask,
showConfigPage,
+ showInfoPage,
} = this.props
return h(Menu, { className: 'account-menu', isShowing: isAccountMenuOpen }, [
h(CloseArea, { onClick: toggleAccountMenu }),
h(Item, {
className: 'account-menu__header',
- onClick: lockMetamask,
}, [
'My Accounts',
- h('button.account-menu__logout-button', 'Log out'),
+ h('button.account-menu__logout-button', {
+ onClick: lockMetamask,
+ }, 'Log out'),
]),
h(Divider),
h('div.account-menu__accounts', this.renderAccounts()),
@@ -83,6 +89,7 @@ AccountMenu.prototype.render = function () {
}),
h(Divider),
h(Item, {
+ onClick: showInfoPage,
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: 'Info & Help',
}),
@@ -98,15 +105,14 @@ AccountMenu.prototype.renderAccounts = function () {
const {
identities,
accounts,
- selected,
+ selectedAddress,
keyrings,
showAccountDetail,
} = this.props
- console.log({ accounts })
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
- const isSelected = identity.address === selected
+ const isSelected = identity.address === selectedAddress
const balanceValue = accounts[key] ? accounts[key].balance : ''
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
@@ -122,7 +128,7 @@ AccountMenu.prototype.renderAccounts = function () {
{ onClick: () => showAccountDetail(identity.address) },
[
h('div.account-menu__check-mark', [
- isSelected ? h('i.fa.fa-check') : null,
+ isSelected ? h('div.account-menu__check-mark-icon') : null,
]),
h(
@@ -148,6 +154,6 @@ AccountMenu.prototype.indicateIfLoose = function (keyring) {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
const isLoose = type !== 'HD Key Tree'
- return isLoose ? h('.keyring-label', 'LOOSE') : null
+ return isLoose ? h('.keyring-label', 'IMPORTED') : null
} catch (e) { return }
}
diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js
index d84834d06..22e37602e 100644
--- a/ui/app/components/bn-as-decimal-input.js
+++ b/ui/app/components/bn-as-decimal-input.js
@@ -31,6 +31,8 @@ BnAsDecimalInput.prototype.render = function () {
const suffix = props.suffix
const style = props.style
const valueString = value.toString(10)
+ const newMin = min && this.downsize(min.toString(10), scale)
+ const newMax = max && this.downsize(max.toString(10), scale)
const newValue = this.downsize(valueString, scale)
return (
@@ -47,8 +49,8 @@ BnAsDecimalInput.prototype.render = function () {
type: 'number',
step: 'any',
required: true,
- min,
- max,
+ min: newMin,
+ max: newMax,
style: extend({
display: 'block',
textAlign: 'right',
@@ -128,15 +130,17 @@ BnAsDecimalInput.prototype.updateValidity = function (event) {
}
BnAsDecimalInput.prototype.constructWarning = function () {
- const { name, min, max } = this.props
+ const { name, min, max, scale, suffix } = this.props
+ const newMin = min && this.downsize(min.toString(10), scale)
+ const newMax = max && this.downsize(max.toString(10), scale)
let message = name ? name + ' ' : ''
if (min && max) {
- message += `must be greater than or equal to ${min} and less than or equal to ${max}.`
+ message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.`
} else if (min) {
- message += `must be greater than or equal to ${min}.`
+ message += `must be greater than or equal to ${newMin} ${suffix}.`
} else if (max) {
- message += `must be less than or equal to ${max}.`
+ message += `must be less than or equal to ${newMax} ${suffix}.`
} else {
message += 'Invalid input.'
}
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index a36f41df5..d5958787b 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -76,7 +76,7 @@ BuyButtonSubview.prototype.headerSubview = function () {
paddingTop: '4px',
paddingBottom: '4px',
},
- }, 'Buy Eth'),
+ }, 'Deposit Eth'),
]),
// loading indication
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 744891c47..722ed2b23 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -6,23 +6,46 @@ const actions = require('../../actions')
const GasModalCard = require('./gas-modal-card')
const {
- MIN_GAS_PRICE,
- MIN_GAS_LIMIT,
+ MIN_GAS_PRICE_DEC,
+ MIN_GAS_LIMIT_DEC,
+ MIN_GAS_PRICE_GWEI,
} = require('../send/send-constants')
-const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
+const {
+ isBalanceSufficient,
+} = require('../send/send-utils')
+
+const {
+ conversionUtil,
+ multiplyCurrencies,
+ conversionGreaterThan,
+} = require('../../conversion-util')
const {
getGasPrice,
getGasLimit,
conversionRateSelector,
+ getSendAmount,
+ getSelectedToken,
+ getSendFrom,
+ getCurrentAccountWithSendEtherInfo,
+ getSelectedTokenToFiatRate,
} = require('../../selectors')
function mapStateToProps (state) {
+ const selectedToken = getSelectedToken(state)
+ const currentAccount = getSendFrom(state) || getCurrentAccountWithSendEtherInfo(state)
+ const conversionRate = conversionRateSelector(state)
+
return {
gasPrice: getGasPrice(state),
gasLimit: getGasLimit(state),
- conversionRate: conversionRateSelector(state),
+ conversionRate,
+ amount: getSendAmount(state),
+ balance: currentAccount.balance,
+ primaryCurrency: selectedToken && selectedToken.symbol,
+ selectedToken,
+ amountConversionRate: selectedToken ? getSelectedTokenToFiatRate(state) : conversionRate,
}
}
@@ -35,19 +58,34 @@ function mapDispatchToProps (dispatch) {
}
}
+function getOriginalState(props) {
+ const gasPrice = props.gasPrice || MIN_GAS_PRICE_DEC
+ const gasLimit = props.gasLimit || MIN_GAS_LIMIT_DEC
+
+ const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+
+ return {
+ gasPrice,
+ gasLimit,
+ gasTotal,
+ error: null,
+ }
+}
+
inherits(CustomizeGasModal, Component)
function CustomizeGasModal (props) {
Component.call(this)
- this.state = {
- gasPrice: props.gasPrice || MIN_GAS_PRICE,
- gasLimit: props.gasLimit || MIN_GAS_LIMIT,
- }
+ this.state = getOriginalState(props)
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(CustomizeGasModal)
-CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) {
+CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) {
const {
updateGasPrice,
updateGasLimit,
@@ -55,41 +93,105 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit) {
updateGasTotal
} = this.props
- const newGasTotal = multiplyCurrencies(gasLimit, gasPrice, {
- toNumericBase: 'hex',
- multiplicandBase: 16,
- multiplierBase: 16,
- })
-
updateGasPrice(gasPrice)
updateGasLimit(gasLimit)
- updateGasTotal(newGasTotal)
+ updateGasTotal(gasTotal)
hideModal()
}
+CustomizeGasModal.prototype.revert = function () {
+ this.setState(getOriginalState(this.props))
+}
+
+CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
+ const {
+ amount,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
+ } = this.props
+
+ let error = null
+
+ const balanceIsSufficient = isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
+ })
+
+ if (!balanceIsSufficient) {
+ error = 'Insufficient balance for current gas total'
+ }
+
+ const gasLimitTooLow = gasLimit && conversionGreaterThan(
+ {
+ value: MIN_GAS_LIMIT_DEC,
+ fromNumericBase: 'dec',
+ conversionRate,
+ },
+ {
+ value: gasLimit,
+ fromNumericBase: 'hex',
+ },
+ )
+
+ if (gasLimitTooLow) {
+ error = 'Gas limit must be at least 21000'
+ }
+
+ this.setState({ error })
+ return error
+}
+
CustomizeGasModal.prototype.convertAndSetGasLimit = function (newGasLimit) {
- const convertedGasLimit = conversionUtil(newGasLimit, {
+ const { gasPrice } = this.state
+
+ const gasLimit = conversionUtil(newGasLimit, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
})
- this.setState({ gasLimit: convertedGasLimit })
+ const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+
+ this.validate({ gasTotal, gasLimit })
+
+ this.setState({ gasTotal, gasLimit })
}
CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) {
- const convertedGasPrice = conversionUtil(newGasPrice, {
+ const { gasLimit } = this.state
+
+ const gasPrice = conversionUtil(newGasPrice, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
fromDenomination: 'GWEI',
toDenomination: 'WEI',
})
- this.setState({ gasPrice: convertedGasPrice })
+ const gasTotal = multiplyCurrencies(gasLimit, gasPrice, {
+ toNumericBase: 'hex',
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ })
+
+ this.validate({ gasTotal })
+
+ this.setState({ gasTotal, gasPrice })
}
CustomizeGasModal.prototype.render = function () {
const { hideModal, conversionRate } = this.props
- const { gasPrice, gasLimit } = this.state
+ const { gasPrice, gasLimit, gasTotal, error } = this.state
const convertedGasPrice = conversionUtil(gasPrice, {
fromNumericBase: 'hex',
@@ -104,7 +206,7 @@ CustomizeGasModal.prototype.render = function () {
})
return h('div.send-v2__customize-gas', {}, [
- h('div', {
+ h('div.send-v2__customize-gas__content', {
}, [
h('div.send-v2__customize-gas__header', {}, [
@@ -120,17 +222,17 @@ CustomizeGasModal.prototype.render = function () {
h(GasModalCard, {
value: convertedGasPrice,
- min: MIN_GAS_PRICE,
+ min: MIN_GAS_PRICE_GWEI,
// max: 1000,
step: 1,
onChange: value => this.convertAndSetGasPrice(value),
- title: 'Gas Price',
+ title: 'Gas Price (GWEI)',
copy: 'We calculate the suggested gas prices based on network success rates.',
}),
h(GasModalCard, {
value: convertedGasLimit,
- min: MIN_GAS_LIMIT,
+ min: 1,
// max: 100000,
step: 1,
onChange: value => this.convertAndSetGasLimit(value),
@@ -141,9 +243,13 @@ CustomizeGasModal.prototype.render = function () {
]),
h('div.send-v2__customize-gas__footer', {}, [
+
+ error && h('div.send-v2__customize-gas__error-message', [
+ error,
+ ]),
h('div.send-v2__customize-gas__revert', {
- onClick: () => console.log('Revert'),
+ onClick: () => this.revert(),
}, ['Revert']),
h('div.send-v2__customize-gas__buttons', [
@@ -151,8 +257,8 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal,
}, ['CANCEL']),
- h('div.send-v2__customize-gas__save', {
- onClick: () => this.save(gasPrice, gasLimit),
+ h(`div.send-v2__customize-gas__save${error ? '__error' : ''}`, {
+ onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal),
}, ['SAVE']),
])
diff --git a/ui/app/components/dropdowns/account-dropdown-mini.js b/ui/app/components/dropdowns/account-dropdown-mini.js
new file mode 100644
index 000000000..96057d2b4
--- /dev/null
+++ b/ui/app/components/dropdowns/account-dropdown-mini.js
@@ -0,0 +1,78 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const Identicon = require('../identicon')
+const AccountListItem = require('../send/account-list-item')
+
+module.exports = AccountDropdownMini
+
+inherits(AccountDropdownMini, Component)
+function AccountDropdownMini () {
+ Component.call(this)
+}
+
+AccountDropdownMini.prototype.getListItemIcon = function (currentAccount, selectedAccount) {
+ const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
+
+ return currentAccount.address === selectedAccount.address
+ ? listItemIcon
+ : null
+}
+
+AccountDropdownMini.prototype.renderDropdown = function () {
+ const {
+ accounts,
+ selectedAccount,
+ closeDropdown,
+ onSelect,
+ } = this.props
+
+ return h('div', {}, [
+
+ h('div.account-dropdown-mini__close-area', {
+ onClick: closeDropdown,
+ }),
+
+ h('div.account-dropdown-mini__list', {}, [
+
+ ...accounts.map(account => h(AccountListItem, {
+ account,
+ displayBalance: false,
+ displayAddress: false,
+ handleClick: () => {
+ onSelect(account)
+ closeDropdown()
+ },
+ icon: this.getListItemIcon(account, selectedAccount),
+ }))
+
+ ]),
+
+ ])
+}
+
+AccountDropdownMini.prototype.render = function () {
+ const {
+ accounts,
+ selectedAccount,
+ openDropdown,
+ closeDropdown,
+ dropdownOpen,
+ } = this.props
+
+ return h('div.account-dropdown-mini', {}, [
+
+ h(AccountListItem, {
+ account: selectedAccount,
+ handleClick: openDropdown,
+ displayBalance: false,
+ displayAddress: false,
+ icon: h(`i.fa.fa-caret-down.fa-lg`, { style: { color: '#dedede' } })
+ }),
+
+ dropdownOpen && this.renderDropdown(),
+
+ ])
+
+}
+
diff --git a/ui/app/components/dropdowns/components/dropdown.js b/ui/app/components/dropdowns/components/dropdown.js
index 991c89cb8..ca68e55f7 100644
--- a/ui/app/components/dropdowns/components/dropdown.js
+++ b/ui/app/components/dropdowns/components/dropdown.js
@@ -65,6 +65,9 @@ Dropdown.propTypes = {
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
style: PropTypes.object.isRequired,
+ onClickOutside: PropTypes.func,
+ innerStyle: PropTypes.object,
+ useCssTransition: PropTypes.bool,
}
class DropdownMenuItem extends Component {
@@ -100,6 +103,7 @@ DropdownMenuItem.propTypes = {
closeMenu: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
children: PropTypes.node,
+ style: PropTypes.object,
}
module.exports = {
diff --git a/ui/app/components/dropdowns/simple-dropdown.js b/ui/app/components/dropdowns/simple-dropdown.js
new file mode 100644
index 000000000..8cea78518
--- /dev/null
+++ b/ui/app/components/dropdowns/simple-dropdown.js
@@ -0,0 +1,91 @@
+const { Component, PropTypes } = require('react')
+const h = require('react-hyperscript')
+const classnames = require('classnames')
+const R = require('ramda')
+
+class SimpleDropdown extends Component {
+ constructor (props) {
+ super(props)
+ this.state = {
+ isOpen: false,
+ }
+ }
+
+ getDisplayValue () {
+ const { selectedOption, options } = this.props
+ const matchesOption = option => option.value === selectedOption
+ const matchingOption = R.find(matchesOption)(options)
+ return matchingOption
+ ? matchingOption.displayValue || matchingOption.value
+ : selectedOption
+ }
+
+ handleClose () {
+ this.setState({ isOpen: false })
+ }
+
+ toggleOpen () {
+ const { isOpen } = this.state
+ this.setState({ isOpen: !isOpen })
+ }
+
+ renderOptions () {
+ const { options, onSelect, selectedOption } = this.props
+
+ return h('div', [
+ h('div.simple-dropdown__close-area', {
+ onClick: event => {
+ event.stopPropagation()
+ this.handleClose()
+ },
+ }),
+ h('div.simple-dropdown__options', [
+ ...options.map(option => {
+ return h(
+ 'div.simple-dropdown__option',
+ {
+ className: classnames({
+ 'simple-dropdown__option--selected': option.value === selectedOption,
+ }),
+ key: option.value,
+ onClick: () => {
+ if (option.value !== selectedOption) {
+ onSelect(option.value)
+ }
+
+ this.handleClose()
+ },
+ },
+ option.displayValue || option.value,
+ )
+ }),
+ ]),
+ ])
+ }
+
+ render () {
+ const { placeholder } = this.props
+ const { isOpen } = this.state
+
+ return h(
+ 'div.simple-dropdown',
+ {
+ onClick: () => this.toggleOpen(),
+ },
+ [
+ h('div.simple-dropdown__selected', this.getDisplayValue() || placeholder || 'Select'),
+ h('i.fa.fa-caret-down.fa-lg.simple-dropdown__caret'),
+ isOpen && this.renderOptions(),
+ ]
+ )
+ }
+}
+
+SimpleDropdown.propTypes = {
+ options: PropTypes.array.isRequired,
+ placeholder: PropTypes.string,
+ onSelect: PropTypes.func,
+ selectedOption: PropTypes.string,
+}
+
+module.exports = SimpleDropdown
diff --git a/ui/app/components/editable-label.js b/ui/app/components/editable-label.js
index 167be7eaf..eb41ec50c 100644
--- a/ui/app/components/editable-label.js
+++ b/ui/app/components/editable-label.js
@@ -1,56 +1,88 @@
-const Component = require('react').Component
+const { Component } = require('react')
+const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const findDOMNode = require('react-dom').findDOMNode
+const classnames = require('classnames')
-module.exports = EditableLabel
+class EditableLabel extends Component {
+ constructor (props) {
+ super(props)
-inherits(EditableLabel, Component)
-function EditableLabel () {
- Component.call(this)
-}
+ this.state = {
+ isEditing: false,
+ value: props.defaultValue || '',
+ }
+ }
+
+ handleSubmit () {
+ const { value } = this.state
+
+ if (value === '') {
+ return
+ }
+
+ Promise.resolve(this.props.onSubmit(value))
+ .then(() => this.setState({ isEditing: false }))
+ }
+
+ saveIfEnter (event) {
+ if (event.key === 'Enter') {
+ this.handleSubmit()
+ }
+ }
-EditableLabel.prototype.render = function () {
- const props = this.props
- const state = this.state
+ renderEditing () {
+ const { value } = this.state
- if (state && state.isEditingLabel) {
- return h('div.editable-label', [
- h('input.sizing-input', {
- defaultValue: props.textValue,
- maxLength: '20',
+ return ([
+ h('input.large-input.editable-label__input', {
+ type: 'text',
+ required: true,
+ value: this.state.value,
onKeyPress: (event) => {
- this.saveIfEnter(event)
+ if (event.key === 'Enter') {
+ this.handleSubmit()
+ }
},
+ onChange: event => this.setState({ value: event.target.value }),
+ className: classnames({ 'editable-label__input--error': value === '' }),
}),
- h('button.editable-button', {
- onClick: () => this.saveText(),
- }, 'Save'),
+ h('div.editable-label__icon-wrapper', [
+ h('i.fa.fa-check.editable-label__icon', {
+ onClick: () => this.handleSubmit(),
+ }),
+ ]),
])
- } else {
- return h('div.name-label', {
- onClick: (event) => {
- const nameAttribute = event.target.getAttribute('name')
- // checks for class to handle smaller CTA above the account name
- const classAttribute = event.target.getAttribute('class')
- if (nameAttribute === 'edit' || classAttribute === 'edit-text') {
- this.setState({ isEditingLabel: true })
- }
- },
- }, this.props.children)
}
-}
-EditableLabel.prototype.saveIfEnter = function (event) {
- if (event.key === 'Enter') {
- this.saveText()
+ renderReadonly () {
+ return ([
+ h('div.editable-label__value', this.state.value),
+ h('div.editable-label__icon-wrapper', [
+ h('i.fa.fa-pencil.editable-label__icon', {
+ onClick: () => this.setState({ isEditing: true }),
+ }),
+ ]),
+ ])
+ }
+
+ render () {
+ const { isEditing } = this.state
+ const { className } = this.props
+
+ return (
+ h('div.editable-label', { className: classnames(className) },
+ isEditing
+ ? this.renderEditing()
+ : this.renderReadonly()
+ )
+ )
}
}
-EditableLabel.prototype.saveText = function () {
- var container = findDOMNode(this)
- var text = container.querySelector('.editable-label input').value
- var truncatedText = text.substring(0, 20)
- this.props.saveText(truncatedText)
- this.setState({ isEditingLabel: false, textLabel: truncatedText })
+EditableLabel.propTypes = {
+ onSubmit: PropTypes.func.isRequired,
+ defaultValue: PropTypes.string,
+ className: PropTypes.string,
}
+
+module.exports = EditableLabel
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index fb8c8e579..6553053f7 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -6,7 +6,7 @@ const debounce = require('debounce')
const copyToClipboard = require('copy-to-clipboard')
const ENS = require('ethjs-ens')
const networkMap = require('ethjs-ens/lib/network-map.json')
-const ensRE = /.+\.eth$/
+const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
index 259fa4d73..d30b7cd56 100644
--- a/ui/app/components/identicon.js
+++ b/ui/app/components/identicon.js
@@ -55,6 +55,7 @@ IdenticonComponent.prototype.componentDidMount = function () {
if (!address) return
+ // eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
var diameter = props.diameter || this.defaultDiameter
@@ -70,6 +71,7 @@ IdenticonComponent.prototype.componentDidUpdate = function () {
if (!address) return
+ // eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
var children = container.children
diff --git a/ui/app/components/menu-droppo.js b/ui/app/components/menu-droppo.js
index 95b75f54c..c80bee2be 100644
--- a/ui/app/components/menu-droppo.js
+++ b/ui/app/components/menu-droppo.js
@@ -97,6 +97,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () {
if (this && document.body) {
this.globalClickHandler = this.globalClickOccurred.bind(this)
document.body.addEventListener('click', this.globalClickHandler)
+ // eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
this.container = container
}
@@ -110,6 +111,7 @@ MenuDroppoComponent.prototype.componentWillUnmount = function () {
MenuDroppoComponent.prototype.globalClickOccurred = function (event) {
const target = event.target
+ // eslint-disable-next-line react/no-find-dom-node
const container = findDOMNode(this)
if (target !== container &&
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js
index 37a62e1c0..e3c936702 100644
--- a/ui/app/components/modals/account-details-modal.js
+++ b/ui/app/components/modals/account-details-modal.js
@@ -7,6 +7,7 @@ const AccountModalContainer = require('./account-modal-container')
const { getSelectedIdentity, getSelectedAddress } = require('../../selectors')
const genAccountLink = require('../../../lib/account-link.js')
const QrView = require('../qr-code')
+const EditableLabel = require('../editable-label')
function mapStateToProps (state) {
return {
@@ -23,6 +24,7 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
},
hideModal: () => dispatch(actions.hideModal()),
+ saveAccountLabel: (address, label) => dispatch(actions.saveAccountLabel(address, label)),
}
}
@@ -41,14 +43,19 @@ AccountDetailsModal.prototype.render = function () {
selectedIdentity,
network,
showExportPrivateKeyModal,
- hideModal,
+ saveAccountLabel,
} = this.props
const { name, address } = selectedIdentity
return h(AccountModalContainer, {}, [
+ h(EditableLabel, {
+ className: 'account-modal__name',
+ defaultValue: name,
+ onSubmit: label => saveAccountLabel(address, label),
+ }),
+
h(QrView, {
Qr: {
- message: name,
data: address,
},
}),
@@ -57,14 +64,12 @@ AccountDetailsModal.prototype.render = function () {
h('button.btn-clear', {
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
- }, [ 'View account on Etherscan' ]),
+ }, 'View account on Etherscan'),
// Holding on redesign for Export Private Key functionality
h('button.btn-clear', {
- onClick: () => {
- showExportPrivateKeyModal()
- },
- }, [ 'Export private key' ]),
-
+ onClick: () => showExportPrivateKeyModal(),
+ }, 'Export private key'),
+
])
}
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
index f1a5aa9fd..33615c483 100644
--- a/ui/app/components/modals/buy-options-modal.js
+++ b/ui/app/components/modals/buy-options-modal.js
@@ -42,7 +42,7 @@ BuyOptions.prototype.render = function () {
h('div.buy-modal-content-title', {
style: {},
}, 'Transfers'),
- h('div', {}, 'How would you like to buy Ether?'),
+ h('div', {}, 'How would you like to deposit Ether?'),
]),
h('div.buy-modal-content-options.flex-column.flex-center', {}, [
@@ -54,7 +54,7 @@ BuyOptions.prototype.render = function () {
},
}, [
h('div.buy-modal-content-option-title', {}, 'Coinbase'),
- h('div.buy-modal-content-option-subtitle', {}, 'Buy with Fiat'),
+ h('div.buy-modal-content-option-subtitle', {}, 'Deposit with Fiat'),
]),
// h('div.buy-modal-content-option', {}, [
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js
index 302596eda..2d8470634 100644
--- a/ui/app/components/modals/export-private-key-modal.js
+++ b/ui/app/components/modals/export-private-key-modal.js
@@ -66,7 +66,6 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
})
: h('input.private-key-password-input', {
type: 'password',
- placeholder: 'Type password',
onChange: event => this.setState({ password: event.target.value }),
})
}
@@ -84,7 +83,7 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password,
(privateKey
? this.renderButton('btn-clear', () => hideModal(), 'Done')
- : this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Download')
+ : this.renderButton('btn-clear', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Show')
),
])
@@ -118,7 +117,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h('div.account-modal-divider'),
- h('span.modal-body-title', 'Download Private Keys'),
+ h('span.modal-body-title', 'Show Private Keys'),
h('div.private-key-password', {}, [
this.renderPasswordLabel(privateKey),
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 88deb2bb0..e15dd6c1b 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -162,10 +162,9 @@ const MODALS = {
h(CustomizeGasModal, {}, []),
],
mobileModalStyle: {
- width: '355px',
- height: '598px',
- // top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
- top: '5%',
+ width: '100vw',
+ height: '100vh',
+ top: '0',
transform: 'none',
left: '0',
right: '0',
diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/modals/new-account-modal.js
index 25beb6745..b78de1d8d 100644
--- a/ui/app/components/modals/new-account-modal.js
+++ b/ui/app/components/modals/new-account-modal.js
@@ -28,6 +28,7 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.hideModal())
})
},
+ showImportPage: () => dispatch(actions.showImportPage()),
}
}
@@ -36,7 +37,7 @@ function NewAccountModal () {
Component.call(this)
this.state = {
- newAccountName: ''
+ newAccountName: '',
}
}
@@ -63,7 +64,7 @@ NewAccountModal.prototype.render = function () {
h('div.new-account-input-wrapper', {}, [
h('input.new-account-input', {
placeholder: 'E.g. My new account',
- onChange: (event) => this.setState({ newAccountName: event.target.value })
+ onChange: event => this.setState({ newAccountName: event.target.value }),
}, []),
]),
@@ -71,13 +72,16 @@ NewAccountModal.prototype.render = function () {
'or',
]),
- h('div.new-account-modal-content.after-input', {}, [
- 'Import an account',
- ]),
+ h('div.new-account-modal-content.after-input.pointer', {
+ onClick: () => {
+ this.props.hideModal()
+ this.props.showImportPage()
+ },
+ }, 'Import an account'),
h('div.new-account-modal-content.button', {}, [
h('button.btn-clear', {
- onClick: () => this.props.createAccount(newAccountName)
+ onClick: () => this.props.createAccount(newAccountName),
}, [
'SAVE',
]),
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index b24505750..229d02e36 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -64,13 +64,18 @@ Network.prototype.render = function () {
return (
h('div.network-component.pointer', {
className: classnames('network-component pointer', {
+ 'network-component--disabled': this.props.disabled,
'ethereum-network': providerName === 'mainnet',
'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
'kovan-test-network': providerName === 'kovan',
'rinkeby-test-network': providerName === 'rinkeby',
}),
title: hoverText,
- onClick: (event) => this.props.onClick(event),
+ onClick: (event) => {
+ if (!this.props.disabled) {
+ this.props.onClick(event)
+ }
+ },
}, [
(function () {
switch (iconName) {
diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js
index abfff1f5c..941ac33e6 100644
--- a/ui/app/components/notice.js
+++ b/ui/app/components/notice.js
@@ -117,6 +117,7 @@ Notice.prototype.render = function () {
}
Notice.prototype.componentDidMount = function () {
+ // eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.setupListener(node)
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
@@ -125,6 +126,7 @@ Notice.prototype.componentDidMount = function () {
}
Notice.prototype.componentWillUnmount = function () {
+ // eslint-disable-next-line react/no-find-dom-node
var node = findDOMNode(this)
linker.teardownListener(node)
}
diff --git a/ui/app/components/pending-personal-msg.js b/ui/app/components/pending-personal-msg.js
deleted file mode 100644
index 4542adb28..000000000
--- a/ui/app/components/pending-personal-msg.js
+++ /dev/null
@@ -1,47 +0,0 @@
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const PendingTxDetails = require('./pending-personal-msg-details')
-
-module.exports = PendingMsg
-
-inherits(PendingMsg, Component)
-function PendingMsg () {
- Component.call(this)
-}
-
-PendingMsg.prototype.render = function () {
- var state = this.props
- var msgData = state.txData
-
- return (
-
- h('div', {
- key: msgData.id,
- }, [
-
- // header
- h('h3', {
- style: {
- fontWeight: 'bold',
- textAlign: 'center',
- },
- }, 'Sign Message'),
-
- // message details
- h(PendingTxDetails, state),
-
- // sign + cancel
- h('.flex-row.flex-space-around', [
- h('button', {
- onClick: state.cancelPersonalMessage,
- }, 'Cancel'),
- h('button', {
- onClick: state.signPersonalMessage,
- }, 'Sign'),
- ]),
- ])
-
- )
-}
-
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 7162c7122..2f178f179 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -50,7 +50,7 @@ ConfirmSendEther.prototype.getAmount = function () {
const { conversionRate, currentCurrency } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
- console.log(`conversionRate, currentCurrency`, conversionRate, currentCurrency);
+
const FIAT = conversionUtil(txParams.value, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
@@ -194,7 +194,7 @@ ConfirmSendEther.prototype.render = function () {
this.inputs = []
return (
- h('div.confirm-screen-container', {
+ h('div.confirm-screen-container.confirm-send-ether', {
style: { minWidth: '355px' },
}, [
// Main Send token Card
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index a4c3d16e3..abb7a0770 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -224,7 +224,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
]),
h('div.confirm-screen-section-column', [
- h('div.confirm-screen-row-info', `${fiatAmount + fiatGas} ${currentCurrency}`),
+ h('div.confirm-screen-row-info', `${addCurrencies(fiatAmount, fiatGas)} ${currentCurrency}`),
h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`),
]),
])
@@ -263,7 +263,7 @@ ConfirmSendToken.prototype.render = function () {
this.inputs = []
return (
- h('div.confirm-screen-container', {
+ h('div.confirm-screen-container.confirm-send-token', {
style: { minWidth: '355px' },
}, [
// Main Send token Card
diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js
index cc723df14..83885539c 100644
--- a/ui/app/components/qr-code.js
+++ b/ui/app/components/qr-code.js
@@ -29,11 +29,11 @@ QrCodeView.prototype.render = function () {
const qrImage = qrCode(4, 'M')
qrImage.addData(address)
qrImage.make()
- return h('.div.flex-column.flex-center', {
- style: {
- },
- }, [
- Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message),
+
+ return h('.div.flex-column.flex-center', [
+ Array.isArray(Qr.message)
+ ? h('.message-container', this.renderMultiMessage())
+ : Qr.message && h('.qr-header', Qr.message),
this.props.warning ? this.props.warning && h('span.error.flex-center', {
style: {
diff --git a/ui/app/components/send/account-list-item.js b/ui/app/components/send/account-list-item.js
index ba7eec940..cc514cbd4 100644
--- a/ui/app/components/send/account-list-item.js
+++ b/ui/app/components/send/account-list-item.js
@@ -27,6 +27,8 @@ AccountListItem.prototype.render = function () {
icon = null,
conversionRate,
currentCurrency,
+ displayBalance = true,
+ displayAddress = false,
} = this.props
const { name, address, balance } = account || {}
@@ -46,13 +48,15 @@ AccountListItem.prototype.render = function () {
},
),
- h('div.account-list-item__account-name', {}, name),
+ h('div.account-list-item__account-name', {}, name || address),
icon && h('div.account-list-item__icon', [icon]),
]),
- h(CurrencyDisplay, {
+ displayAddress && name && h('div.account-list-item__account-address', address),
+
+ displayBalance && h(CurrencyDisplay, {
primaryCurrency: 'ETH',
convertedCurrency: currentCurrency,
value: balance,
diff --git a/ui/app/components/send/currency-display.js b/ui/app/components/send/currency-display.js
index 7180b94d3..5dba6a8dd 100644
--- a/ui/app/components/send/currency-display.js
+++ b/ui/app/components/send/currency-display.js
@@ -2,7 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Identicon = require('../identicon')
-const { conversionUtil } = require('../../conversion-util')
+const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
module.exports = CurrencyDisplay
@@ -20,14 +20,6 @@ function isValidInput (text) {
return re.test(text)
}
-function resetCaretIfPastEnd (value, event) {
- const caretPosition = event.target.selectionStart
-
- if (caretPosition > value.length) {
- event.target.setSelectionRange(value.length, value.length)
- }
-}
-
function toHexWei (value) {
return conversionUtil(value, {
fromNumericBase: 'dec',
@@ -40,7 +32,9 @@ CurrencyDisplay.prototype.getAmount = function (value) {
const { selectedToken } = this.props
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
- const sendAmount = '0x' + Number(value * multiplier).toString(16)
+
+ const sendAmount = multiplyCurrencies(value, multiplier, {toNumericBase: 'hex'})
+
return selectedToken
? sendAmount
: toHexWei(value)
@@ -80,6 +74,8 @@ CurrencyDisplay.prototype.render = function () {
conversionRate,
})
+ const inputSizeMultiplier = readOnly ? 1 : 1.2;
+
return h('div', {
className,
style: {
@@ -93,35 +89,33 @@ CurrencyDisplay.prototype.render = function () {
h('input', {
className: primaryBalanceClassName,
- value: `${value || initValueToRender} ${primaryCurrency}`,
- placeholder: `${0} ${primaryCurrency}`,
+ value: `${value || initValueToRender}`,
+ placeholder: '0',
+ size: (value || initValueToRender).length * inputSizeMultiplier,
readOnly,
onChange: (event) => {
- let newValue = event.target.value.split(' ')[0]
+ let newValue = event.target.value
if (newValue === '') {
- this.setState({ value: '0' })
+ newValue = '0'
}
else if (newValue.match(/^0[1-9]$/)) {
- this.setState({ value: newValue.match(/[1-9]/)[0] })
+ newValue = newValue.match(/[1-9]/)[0]
}
- else if (newValue && !isValidInput(newValue)) {
+
+ if (newValue && !isValidInput(newValue)) {
event.preventDefault()
}
else {
+ validate(this.getAmount(newValue))
this.setState({ value: newValue })
}
},
- onBlur: event => !readOnly && handleChange(this.getAmount(event.target.value.split(' ')[0])),
- onKeyUp: event => {
- if (!readOnly) {
- validate(toHexWei(value || initValueToRender))
- resetCaretIfPastEnd(value || initValueToRender, event)
- }
- },
- onClick: event => !readOnly && resetCaretIfPastEnd(value || initValueToRender, event),
+ onBlur: event => !readOnly && handleChange(this.getAmount(event.target.value)),
}),
+ h('span.currency-display__currency-symbol', primaryCurrency),
+
]),
]),
diff --git a/ui/app/components/send/send-constants.js b/ui/app/components/send/send-constants.js
index a819a8c28..8b56607cc 100644
--- a/ui/app/components/send/send-constants.js
+++ b/ui/app/components/send/send-constants.js
@@ -3,12 +3,19 @@ const { multiplyCurrencies } = require('../../conversion-util')
const MIN_GAS_PRICE_GWEI = '1'
const GWEI_FACTOR = '1e9'
-const MIN_GAS_PRICE = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
+const MIN_GAS_PRICE_HEX = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
multiplicandBase: 16,
multiplierBase: 16,
+ toNumericBase: 'hex',
+})
+const MIN_GAS_PRICE_DEC = multiplyCurrencies(GWEI_FACTOR, MIN_GAS_PRICE_GWEI, {
+ multiplicandBase: 16,
+ multiplierBase: 16,
+ toNumericBase: 'dec',
})
-const MIN_GAS_LIMIT = (21000).toString(16)
-const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT, MIN_GAS_PRICE, {
+const MIN_GAS_LIMIT_HEX = (21000).toString(16)
+const MIN_GAS_LIMIT_DEC = 21000
+const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT_HEX, MIN_GAS_PRICE_HEX, {
toNumericBase: 'hex',
multiplicandBase: 16,
multiplierBase: 16,
@@ -16,8 +23,9 @@ const MIN_GAS_TOTAL = multiplyCurrencies(MIN_GAS_LIMIT, MIN_GAS_PRICE, {
module.exports = {
MIN_GAS_PRICE_GWEI,
- GWEI_FACTOR,
- MIN_GAS_PRICE,
- MIN_GAS_LIMIT,
+ MIN_GAS_PRICE_HEX,
+ MIN_GAS_PRICE_DEC,
+ MIN_GAS_LIMIT_HEX,
+ MIN_GAS_LIMIT_DEC,
MIN_GAS_TOTAL,
}
diff --git a/ui/app/components/send/send-utils.js b/ui/app/components/send/send-utils.js
new file mode 100644
index 000000000..bf096d610
--- /dev/null
+++ b/ui/app/components/send/send-utils.js
@@ -0,0 +1,39 @@
+const { addCurrencies, conversionGreaterThan } = require('../../conversion-util')
+
+function isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
+}) {
+ const totalAmount = addCurrencies(amount, gasTotal, {
+ aBase: 16,
+ bBase: 16,
+ toNumericBase: 'hex',
+ })
+
+ const balanceIsSufficient = conversionGreaterThan(
+ {
+ value: balance,
+ fromNumericBase: 'hex',
+ fromCurrency: primaryCurrency,
+ conversionRate,
+ },
+ {
+ value: totalAmount,
+ fromNumericBase: 'hex',
+ conversionRate: amountConversionRate,
+ fromCurrency: selectedToken || primaryCurrency,
+ conversionRate: amountConversionRate,
+ },
+ )
+
+ return balanceIsSufficient
+}
+
+module.exports = {
+ isBalanceSufficient,
+} \ No newline at end of file
diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js
index c14865e9f..fb2634de2 100644
--- a/ui/app/components/send/send-v2-container.js
+++ b/ui/app/components/send/send-v2-container.js
@@ -17,6 +17,7 @@ const {
getAddressBook,
getSendFrom,
getCurrentCurrency,
+ getSelectedTokenToFiatRate,
} = require('../../selectors')
module.exports = connect(mapStateToProps, mapDispatchToProps)(SendEther)
@@ -26,7 +27,6 @@ function mapStateToProps (state) {
const selectedAddress = getSelectedAddress(state)
const selectedToken = getSelectedToken(state)
const tokenExchangeRates = state.metamask.tokenExchangeRates
- const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
const conversionRate = conversionRateSelector(state)
let data;
@@ -40,11 +40,7 @@ function mapStateToProps (state) {
primaryCurrency = selectedToken.symbol
- tokenToFiatRate = multiplyCurrencies(
- conversionRate,
- selectedTokenExchangeRate,
- { toNumericBase: 'dec' }
- )
+ tokenToFiatRate = getSelectedTokenToFiatRate(state)
}
return {
@@ -80,5 +76,6 @@ function mapDispatchToProps (dispatch) {
updateSendMemo: newMemo => dispatch(actions.updateSendMemo(newMemo)),
updateSendErrors: newError => dispatch(actions.updateSendErrors(newError)),
goHome: () => dispatch(actions.goHome()),
+ clearSend: () => dispatch(actions.clearSend())
}
}
diff --git a/ui/app/components/send/to-autocomplete.js b/ui/app/components/send/to-autocomplete.js
index 686a7a23e..ab490155b 100644
--- a/ui/app/components/send/to-autocomplete.js
+++ b/ui/app/components/send/to-autocomplete.js
@@ -2,54 +2,118 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Identicon = require('../identicon')
+const AccountListItem = require('./account-list-item')
module.exports = ToAutoComplete
inherits(ToAutoComplete, Component)
function ToAutoComplete () {
Component.call(this)
+
+ this.state = { accountsToRender: [] }
+}
+
+ToAutoComplete.prototype.getListItemIcon = function (listItemAddress, toAddress) {
+ const listItemIcon = h(`i.fa.fa-check.fa-lg`, { style: { color: '#02c9b1' } })
+
+ return toAddress && listItemAddress === toAddress
+ ? listItemIcon
+ : null
+}
+
+ToAutoComplete.prototype.renderDropdown = function () {
+ const {
+ accounts,
+ closeDropdown,
+ onChange,
+ to,
+ } = this.props
+ const { accountsToRender } = this.state
+
+ return accountsToRender.length && h('div', {}, [
+
+ h('div.send-v2__from-dropdown__close-area', {
+ onClick: closeDropdown,
+ }),
+
+ h('div.send-v2__from-dropdown__list', {}, [
+
+ ...accountsToRender.map(account => h(AccountListItem, {
+ account,
+ handleClick: () => {
+ onChange(account.address)
+ closeDropdown()
+ },
+ icon: this.getListItemIcon(account.address, to),
+ displayBalance: false,
+ displayAddress: true,
+ }))
+
+ ]),
+
+ ])
+}
+
+ToAutoComplete.prototype.handleInputEvent = function (event = {}, cb) {
+ const {
+ to,
+ accounts,
+ closeDropdown,
+ openDropdown,
+ } = this.props
+
+ const matchingAccounts = accounts.filter(({ address }) => address.match(to || ''))
+ const matches = matchingAccounts.length
+
+ if (!matches || matchingAccounts[0].address === to) {
+ this.setState({ accountsToRender: [] })
+ event.target && event.target.select()
+ closeDropdown()
+ }
+ else {
+ this.setState({ accountsToRender: matchingAccounts })
+ openDropdown()
+ }
+ cb && cb(event.target.value)
+}
+
+ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
+ if (this.props.to !== nextProps.to) {
+ this.handleInputEvent()
+ }
}
ToAutoComplete.prototype.render = function () {
- const { to, accounts, onChange, inError } = this.props
+ const {
+ to,
+ accounts,
+ openDropdown,
+ closeDropdown,
+ dropdownOpen,
+ onChange,
+ inError,
+ } = this.props
- return h('div.send-v2__to-autocomplete', [
+ return h('div.to-autocomplete', {}, [
h('input.send-v2__to-autocomplete__input', {
- name: 'address',
- list: 'addresses',
placeholder: 'Recipient Address',
className: inError ? `send-v2__error-border` : '',
value: to,
- onChange,
- onFocus: event => {
- to && event.target.select()
- },
+ onChange: event => onChange(event.target.value),
+ onFocus: event => this.handleInputEvent(event),
style: {
borderColor: inError ? 'red' : null,
}
}),
- h('datalist#addresses', [
- // Corresponds to the addresses owned.
- ...Object.entries(accounts).map(([key, { address, name }]) => {
- return h('option', {
- value: address,
- label: name,
- key: address,
- })
- }),
- // Corresponds to previously sent-to addresses.
- // ...addressBook.map(({ address, name }) => {
- // return h('option', {
- // value: address,
- // label: name,
- // key: address,
- // })
- // }),
- ]),
+ !to && h(`i.fa.fa-caret-down.fa-lg.send-v2__to-autocomplete__down-caret`, {
+ style: { color: '#dedede' },
+ onClick: () => this.handleInputEvent(),
+ }),
+
+ dropdownOpen && this.renderDropdown(),
])
-
}
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 96a86d3b1..c5993e3d3 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -130,8 +130,8 @@ ShapeshiftForm.prototype.renderMain = function () {
alignItems: 'flex-start',
},
}, [
- this.props.warning
- ? this.props.warning &&
+ this.props.warning ?
+ this.props.warning &&
h('span.error.flex-center', {
style: {
textAlign: 'center',
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
new file mode 100644
index 000000000..a0ecbe8ec
--- /dev/null
+++ b/ui/app/components/signature-request.js
@@ -0,0 +1,255 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const Identicon = require('./identicon')
+const connect = require('react-redux').connect
+const ethUtil = require('ethereumjs-util')
+const PendingTxDetails = require('./pending-personal-msg-details')
+const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
+const BinaryRenderer = require('./binary-renderer')
+
+const actions = require('../actions')
+const { conversionUtil } = require('../conversion-util')
+
+const {
+ getSelectedAccount,
+ getCurrentAccountWithSendEtherInfo,
+ getSelectedAddress,
+ accountsWithSendEtherInfoSelector,
+ conversionRateSelector,
+} = require('../selectors.js')
+
+function mapStateToProps (state) {
+ return {
+ balance: getSelectedAccount(state).balance,
+ selectedAccount: getCurrentAccountWithSendEtherInfo(state),
+ selectedAddress: getSelectedAddress(state),
+ requester: null,
+ requesterAddress: null,
+ accounts: accountsWithSendEtherInfoSelector(state),
+ conversionRate: conversionRateSelector(state)
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ goHome: () => dispatch(actions.goHome())
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(SignatureRequest)
+
+inherits(SignatureRequest, Component)
+function SignatureRequest (props) {
+ Component.call(this)
+
+ this.state = {
+ selectedAccount: props.selectedAccount,
+ accountDropdownOpen: false,
+ }
+}
+
+SignatureRequest.prototype.renderHeader = function () {
+ return h('div.request-signature__header', [
+
+ h('div.request-signature__header-background'),
+
+ h('div.request-signature__header__text', 'Signature Request'),
+
+ h('div.request-signature__header__tip-container', [
+ h('div.request-signature__header__tip'),
+ ]),
+
+ ])
+}
+
+SignatureRequest.prototype.renderAccountDropdown = function () {
+ const {
+ selectedAccount,
+ accountDropdownOpen,
+ } = this.state
+
+ const {
+ accounts,
+ } = this.props
+
+ return h('div.request-signature__account', [
+
+ h('div.request-signature__account-text', ['Account:']),
+
+ h(AccountDropdownMini, {
+ selectedAccount,
+ accounts,
+ onSelect: selectedAccount => this.setState({ selectedAccount }),
+ dropdownOpen: accountDropdownOpen,
+ openDropdown: () => this.setState({ accountDropdownOpen: true }),
+ closeDropdown: () => this.setState({ accountDropdownOpen: false }),
+ })
+
+ ])
+}
+
+SignatureRequest.prototype.renderBalance = function () {
+ const { balance, conversionRate } = this.props
+
+ const balanceInEther = conversionUtil(balance, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromDenomination: 'WEI',
+ numberOfDecimals: 6,
+ conversionRate,
+ })
+
+ return h('div.request-signature__balance', [
+
+ h('div.request-signature__balance-text', ['Balance:']),
+
+ h('div.request-signature__balance-value', `${balanceInEther} ETH`),
+
+ ])
+}
+
+SignatureRequest.prototype.renderAccountInfo = function () {
+ return h('div.request-signature__account-info', [
+
+ this.renderAccountDropdown(),
+
+ this.renderRequestIcon(),
+
+ this.renderBalance(),
+
+ ])
+}
+
+SignatureRequest.prototype.renderRequestIcon = function () {
+ const { requesterAddress } = this.props
+
+ return h('div.request-signature__request-icon', [
+ h(Identicon, {
+ diameter: 40,
+ address: requesterAddress,
+ })
+ ])
+}
+
+SignatureRequest.prototype.renderRequestInfo = function () {
+ const { requester } = this.props
+
+ return h('div.request-signature__request-info', [
+
+ h('div.request-signature__headline', [
+ `Your signature is being requested`,
+ ])
+
+ ])
+}
+
+SignatureRequest.prototype.msgHexToText = function (hex) {
+ try {
+ const stripped = ethUtil.stripHexPrefix(hex)
+ const buff = Buffer.from(stripped, 'hex')
+ return buff.toString('utf8')
+ } catch (e) {
+ return hex
+ }
+}
+
+SignatureRequest.prototype.renderBody = function () {
+ let rows
+ let notice = 'You are signing:'
+
+ const { txData } = this.props
+ const { type, msgParams: { data } } = txData
+
+ if (type === 'personal_sign') {
+ rows = [{ name: 'Message', value: this.msgHexToText(data) }]
+ }
+ else if (type === 'eth_signTypedData') {
+ rows = data
+ }
+ else if (type === 'eth_sign') {
+ rows = [{ name: 'Message', value: data }]
+ notice = `Signing this message can have
+ dangerous side effects. Only sign messages from
+ sites you fully trust with your entire account.
+ This dangerous method will be removed in a future version. `
+ }
+
+ return h('div.request-signature__body', {}, [
+
+ this.renderAccountInfo(),
+
+ this.renderRequestInfo(),
+
+ h('div.request-signature__notice', [notice]),
+
+ h('div.request-signature__rows', [
+
+ ...rows.map(({ name, value }) => {
+ return h('div.request-signature__row', [
+ h('div.request-signature__row-title', [`${name}:`]),
+ h('div.request-signature__row-value', value),
+ ])
+ }),
+
+ ]),
+
+ ])
+}
+
+SignatureRequest.prototype.renderFooter = function () {
+ const {
+ goHome,
+ signPersonalMessage,
+ signTypedMessage,
+ cancelPersonalMessage,
+ cancelTypedMessage,
+ signMessage,
+ cancelMessage,
+ } = this.props
+
+ const { txData } = this.props
+ const { type } = txData
+
+ let cancel
+ let sign
+ if (type === 'personal_sign') {
+ cancel = cancelPersonalMessage
+ sign = signPersonalMessage
+ }
+ else if (type === 'eth_signTypedData') {
+ cancel = cancelTypedMessage
+ sign = signTypedMessage
+ }
+ else if (type === 'eth_sign') {
+ cancel = cancelMessage
+ sign = signMessage
+ }
+
+ return h('div.request-signature__footer', [
+ h('button.request-signature__footer__cancel-button', {
+ onClick: cancel,
+ }, 'CANCEL'),
+ h('button.request-signature__footer__sign-button', {
+ onClick: sign,
+ }, 'SIGN'),
+ ])
+}
+
+SignatureRequest.prototype.render = function () {
+ return (
+
+ h('div.request-signature__container', [
+
+ this.renderHeader(),
+
+ this.renderBody(),
+
+ this.renderFooter(),
+
+ ])
+
+ )
+
+}
+
diff --git a/ui/app/components/tab-bar.js b/ui/app/components/tab-bar.js
index bef444a48..fe4076ed0 100644
--- a/ui/app/components/tab-bar.js
+++ b/ui/app/components/tab-bar.js
@@ -1,37 +1,40 @@
-const Component = require('react').Component
+const { Component } = require('react')
const h = require('react-hyperscript')
-const inherits = require('util').inherits
+const classnames = require('classnames')
-module.exports = TabBar
+class TabBar extends Component {
+ constructor (props) {
+ super(props)
+ const { defaultTab, tabs } = props
-inherits(TabBar, Component)
-function TabBar () {
- Component.call(this)
-}
+ this.state = {
+ subview: defaultTab || tabs[0].key,
+ }
+ }
-TabBar.prototype.render = function () {
- const props = this.props
- const state = this.state || {}
- const { tabs = [], defaultTab, tabSelected } = props
- const { subview = defaultTab } = state
+ render () {
+ const { tabs = [], tabSelected } = this.props
+ const { subview } = this.state
- return (
- h('.flex-row.space-around.text-transform-uppercase', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- paddingTop: '4px',
- minHeight: '30px',
- },
- }, tabs.map((tab) => {
- const { key, content } = tab
- return h(subview === key ? '.activeForm' : '.inactiveForm.pointer', {
- onClick: () => {
- this.setState({ subview: key })
- tabSelected(key)
- },
- }, content)
- }))
- )
+ return (
+ h('.tab-bar', {}, [
+ tabs.map((tab) => {
+ const { key, content } = tab
+ return h('div', {
+ className: classnames('tab-bar__tab pointer', {
+ 'tab-bar__tab--active': subview === key,
+ }),
+ onClick: () => {
+ this.setState({ subview: key })
+ tabSelected(key)
+ },
+ key,
+ }, content)
+ }),
+ h('div.tab-bar__tab.tab-bar__grow-tab'),
+ ])
+ )
+ }
}
+module.exports = TabBar
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index 59f55d485..ebef22680 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -73,7 +73,7 @@ TxView.prototype.renderButtons = function () {
onClick: () => showModal({
name: 'BUY',
}),
- }, 'BUY'),
+ }, 'DEPOSIT'),
h('button.btn-clear', {
style: {
@@ -109,14 +109,15 @@ TxView.prototype.render = function () {
margin: '1em 0.9em',
alignItems: 'center',
},
- onClick: () => {
- this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar()
- },
}, [
h('div.fa.fa-bars', {
style: {
fontSize: '1.3em',
+ cursor: 'pointer',
+ },
+ onClick: () => {
+ this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar()
},
}, []),
diff --git a/ui/app/components/typed-message-renderer.js b/ui/app/components/typed-message-renderer.js
index a042b57be..d170d63b7 100644
--- a/ui/app/components/typed-message-renderer.js
+++ b/ui/app/components/typed-message-renderer.js
@@ -32,11 +32,11 @@ TypedMessageRenderer.prototype.render = function () {
)
}
-function renderTypedData(values) {
+function renderTypedData (values) {
return values.map(function (value) {
return h('div', {}, [
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'),
h('div', {}, value.value),
])
})
-} \ No newline at end of file
+}
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index a870a24e3..3cb7a8b76 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -3,7 +3,8 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const inherits = require('util').inherits
const Identicon = require('./identicon')
-const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
+// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns
+const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions')
const BalanceComponent = require('./balance-component')
const TokenList = require('./token-list')
@@ -19,6 +20,7 @@ function mapStateToProps (state) {
identities: state.metamask.identities,
accounts: state.metamask.accounts,
tokens: state.metamask.tokens,
+ keyrings: state.metamask.keyrings,
selectedAddress: selectors.getSelectedAddress(state),
selectedIdentity: selectors.getSelectedIdentity(state),
selectedAccount: selectors.getSelectedAccount(state),
@@ -28,15 +30,22 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
- showSendPage: () => { dispatch(actions.showSendPage()) },
- hideSidebar: () => { dispatch(actions.hideSidebar()) },
+ showSendPage: () => dispatch(actions.showSendPage()),
+ hideSidebar: () => dispatch(actions.hideSidebar()),
unsetSelectedToken: () => dispatch(actions.setSelectedToken()),
+ showAccountDetailModal: () => {
+ dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
+ },
+ showAddTokenPage: () => dispatch(actions.showAddTokenPage()),
}
}
inherits(WalletView, Component)
function WalletView () {
Component.call(this)
+ this.state = {
+ hasCopied: false,
+ }
}
WalletView.prototype.renderWalletBalance = function () {
@@ -47,7 +56,7 @@ WalletView.prototype.renderWalletBalance = function () {
hideSidebar,
sidebarOpen,
} = this.props
- console.log({ selectedAccount })
+
const selectedClass = selectedTokenAddress
? ''
: 'wallet-balance-wrapper--active'
@@ -73,13 +82,25 @@ WalletView.prototype.renderWalletBalance = function () {
WalletView.prototype.render = function () {
const {
- network, responsiveDisplayClassname, identities,
- selectedAddress, accounts,
+ responsiveDisplayClassname,
+ selectedAddress,
selectedIdentity,
+ keyrings,
+ showAccountDetailModal,
+ hideSidebar,
+ showAddTokenPage,
} = this.props
// temporary logs + fake extra wallets
// console.log('walletview, selectedAccount:', selectedAccount)
+ const keyring = keyrings.find((kr) => {
+ return kr.accounts.includes(selectedAddress) ||
+ kr.accounts.includes(selectedIdentity.address)
+ })
+
+ const type = keyring.type
+ const isLoose = type !== 'HD Key Tree'
+
return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), {
style: {},
}, [
@@ -88,57 +109,16 @@ WalletView.prototype.render = function () {
h('div.flex-column.wallet-view-account-details', {
style: {},
}, [
+ h('div.wallet-view__sidebar-close', {
+ onClick: hideSidebar,
+ }),
- h('div.flex-row.account-options-menu', {
- style: {
- position: 'relative',
- },
- }, [
-
- h(AccountDropdowns, {
- selected: selectedAddress,
- network,
- identities,
- useCssTransition: true,
- enableAccountOptions: true,
- dropdownWrapperStyle: {
- padding: '1px 15px',
- marginLeft: '-25px',
- position: 'absolute',
- width: '122%', // TODO, refactor all of this component out into media queries
- },
- menuItemStyles: {
- padding: '0px 0px',
- margin: '22px 0px',
- },
- }, []),
-
- ]),
+ h('div.wallet-view__keyring-label', isLoose ? 'IMPORTED' : ''),
- h('div.flex-column.flex-center', {
+ h('div.flex-column.flex-center.wallet-view__name-container', {
+ style: { margin: '0 auto' },
+ onClick: showAccountDetailModal,
}, [
- h('div', {
- style: {
- position: 'relative',
- },
- }, [
- h(AccountDropdowns, {
- accounts,
- style: {
- position: 'absolute',
- left: 'calc(50% + 28px + 5.5px)',
- top: '14px',
- },
- innerStyle: {
- padding: '10px 16px',
- },
- useCssTransition: true,
- selected: selectedAddress,
- network,
- identities,
- }, []),
- ]),
-
h(Identicon, {
diameter: 54,
address: selectedAddress,
@@ -150,21 +130,33 @@ WalletView.prototype.render = function () {
selectedIdentity.name,
]),
+ h('button.wallet-view__details-button', 'DETAILS'),
]),
]),
- // 'Wallet' - Title
- // Not visible on mobile
- h('div.flex-column.wallet-view-title-wrapper', {}, [
- h('span.wallet-view-title', {}, [
- 'Wallet',
- ]),
+
+ h('div.wallet-view__address', {
+ onClick: () => {
+ copyToClipboard(selectedAddress)
+ this.setState({ hasCopied: true })
+ setTimeout(() => this.setState({ hasCopied: false }), 3000)
+ },
+ }, [
+ this.state.hasCopied && 'Copied to Clipboard',
+ !this.state.hasCopied && `${selectedAddress.slice(0, 4)}...${selectedAddress.slice(-4)}`,
+ h('i.fa.fa-clipboard', { style: { marginLeft: '8px' } }),
]),
this.renderWalletBalance(),
h(TokenList),
+ h('button.wallet-view__add-token-button', {
+ onClick: () => {
+ showAddTokenPage()
+ hideSidebar()
+ },
+ }, 'Add Token'),
])
}
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index dfa6f88c4..97e0646e8 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -6,9 +6,10 @@ const actions = require('./actions')
const txHelper = require('../lib/tx-helper')
const PendingTx = require('./components/pending-tx')
-const PendingMsg = require('./components/pending-msg')
-const PendingPersonalMsg = require('./components/pending-personal-msg')
-const PendingTypedMsg = require('./components/pending-typed-msg')
+const SignatureRequest = require('./components/signature-request')
+// const PendingMsg = require('./components/pending-msg')
+// const PendingPersonalMsg = require('./components/pending-personal-msg')
+// const PendingTypedMsg = require('./components/pending-typed-msg')
const Loading = require('./components/loading')
// const contentDivider = h('div', {
@@ -102,8 +103,10 @@ ConfirmTxScreen.prototype.render = function () {
cancelTransaction: this.cancelTransaction.bind(this, txData),
signMessage: this.signMessage.bind(this, txData),
signPersonalMessage: this.signPersonalMessage.bind(this, txData),
+ signTypedMessage: this.signTypedMessage.bind(this, txData),
cancelMessage: this.cancelMessage.bind(this, txData),
cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
+ cancelTypedMessage: this.cancelTypedMessage.bind(this, txData),
})
}
@@ -118,17 +121,19 @@ function currentTxView (opts) {
return h(PendingTx, opts)
} else if (msgParams) {
log.debug('msgParams detected, rendering pending msg')
-
- if (type === 'eth_sign') {
- log.debug('rendering eth_sign message')
- return h(PendingMsg, opts)
- } else if (type === 'personal_sign') {
- log.debug('rendering personal_sign message')
- return h(PendingPersonalMsg, opts)
- } else if (type === 'eth_signTypedData') {
- log.debug('rendering eth_signTypedData message')
- return h(PendingTypedMsg, opts)
- }
+
+ return h(SignatureRequest, opts)
+
+ // if (type === 'eth_sign') {
+ // log.debug('rendering eth_sign message')
+ // return h(PendingMsg, opts)
+ // } else if (type === 'personal_sign') {
+ // log.debug('rendering personal_sign message')
+ // return h(PendingPersonalMsg, opts)
+ // } else if (type === 'eth_signTypedData') {
+ // log.debug('rendering eth_signTypedData message')
+ // return h(PendingTypedMsg, opts)
+ // }
}
return h(Loading)
}
diff --git a/ui/app/config.js b/ui/app/config.js
deleted file mode 100644
index 8b4044882..000000000
--- a/ui/app/config.js
+++ /dev/null
@@ -1,215 +0,0 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
-const h = require('react-hyperscript')
-const connect = require('react-redux').connect
-const actions = require('./actions')
-const infuraCurrencies = require('./infura-conversion.json').objects.sort((a, b) => {
- return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
- })
-const validUrl = require('valid-url')
-const exportAsFile = require('./util').exportAsFile
-
-
-module.exports = connect(mapStateToProps)(ConfigScreen)
-
-function mapStateToProps (state) {
- return {
- metamask: state.metamask,
- warning: state.appState.warning,
- }
-}
-
-inherits(ConfigScreen, Component)
-function ConfigScreen () {
- Component.call(this)
-}
-
-ConfigScreen.prototype.render = function () {
- var state = this.props
- var metamaskState = state.metamask
- var warning = state.warning
-
- return (
- h('.flex-column.flex-grow', { style: { marginTop: '32px' } }, [
-
- // subtitle and nav
- h('.section-title.flex-row.flex-center', [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- onClick: (event) => {
- state.dispatch(actions.goHome())
- },
- }),
- h('h2.page-subtitle', 'Settings'),
- ]),
-
- h('.error', {
- style: {
- display: warning ? 'block' : 'none',
- padding: '0 20px',
- textAlign: 'center',
- },
- }, warning),
-
- // conf view
- h('.flex-column.flex-justify-center.flex-grow.select-none', [
- h('.flex-space-around', {
- style: {
- padding: '20px',
- },
- }, [
-
- currentProviderDisplay(metamaskState),
-
- h('div', { style: {display: 'flex'} }, [
- h('input#new_rpc', {
- placeholder: 'New RPC URL',
- style: {
- width: 'inherit',
- flex: '1 0 auto',
- height: '30px',
- margin: '8px',
- },
- onKeyPress (event) {
- if (event.key === 'Enter') {
- var element = event.target
- var newRpc = element.value
- rpcValidation(newRpc, state)
- }
- },
- }),
- h('button', {
- style: {
- alignSelf: 'center',
- },
- onClick (event) {
- event.preventDefault()
- var element = document.querySelector('input#new_rpc')
- var newRpc = element.value
- rpcValidation(newRpc, state)
- },
- }, 'Save'),
- ]),
-
- h('hr.horizontal-line'),
-
- currentConversionInformation(metamaskState, state),
-
- h('hr.horizontal-line'),
-
- h('div', {
- style: {
- marginTop: '20px',
- },
- }, [
- h('p', {
- style: {
- fontFamily: 'Montserrat Light',
- fontSize: '13px',
- },
- }, `State logs contain your public account addresses and sent transactions.`),
- h('br'),
- h('button', {
- style: {
- alignSelf: 'center',
- },
- onClick (event) {
- exportAsFile('MetaMask State Logs', window.logState())
- },
- }, 'Download State Logs'),
- ]),
-
- h('hr.horizontal-line'),
-
- h('div', {
- style: {
- marginTop: '20px',
- },
- }, [
- h('button', {
- style: {
- alignSelf: 'center',
- },
- onClick (event) {
- event.preventDefault()
- state.dispatch(actions.revealSeedConfirmation())
- },
- }, 'Reveal Seed Words'),
- ]),
-
- ]),
- ]),
- ])
- )
-}
-
-function rpcValidation (newRpc, state) {
- if (validUrl.isWebUri(newRpc)) {
- state.dispatch(actions.setRpcTarget(newRpc))
- } else {
- var appendedRpc = `http://${newRpc}`
- if (validUrl.isWebUri(appendedRpc)) {
- state.dispatch(actions.displayWarning('URIs require the appropriate HTTP/HTTPS prefix.'))
- } else {
- state.dispatch(actions.displayWarning('Invalid RPC URI'))
- }
- }
-}
-
-function currentConversionInformation (metamaskState, state) {
- var currentCurrency = metamaskState.currentCurrency
- var conversionDate = metamaskState.conversionDate
- return h('div', [
- h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, 'Current Conversion'),
- h('span', {style: { fontWeight: 'bold', paddingRight: '10px', fontSize: '13px'}}, `Updated ${Date(conversionDate)}`),
- h('select#currentCurrency', {
- onChange (event) {
- event.preventDefault()
- var element = document.getElementById('currentCurrency')
- var newCurrency = element.value
- state.dispatch(actions.setCurrentCurrency(newCurrency))
- },
- defaultValue: currentCurrency,
- }, infuraCurrencies.map((currency) => {
- console.log(`currency`, currency);
- return h('option', {key: currency.quote.code, value: currency.quote.code}, `${currency.quote.code.toUpperCase()} - ${currency.quote.name}`)
- })
- ),
- ])
-}
-
-function currentProviderDisplay (metamaskState) {
- var provider = metamaskState.provider
- var title, value
-
- switch (provider.type) {
-
- case 'mainnet':
- title = 'Current Network'
- value = 'Main Ethereum Network'
- break
-
- case 'ropsten':
- title = 'Current Network'
- value = 'Ropsten Test Network'
- break
-
- case 'kovan':
- title = 'Current Network'
- value = 'Kovan Test Network'
- break
-
- case 'rinkeby':
- title = 'Current Network'
- value = 'Rinkeby Test Network'
- break
-
- default:
- title = 'Current RPC'
- value = metamaskState.provider.rpcTarget
- }
-
- return h('div', [
- h('span', {style: { fontWeight: 'bold', paddingRight: '10px'}}, title),
- h('span', value),
- ])
-}
diff --git a/ui/app/css/itcss/components/account-dropdown-mini.scss b/ui/app/css/itcss/components/account-dropdown-mini.scss
new file mode 100644
index 000000000..996993db7
--- /dev/null
+++ b/ui/app/css/itcss/components/account-dropdown-mini.scss
@@ -0,0 +1,48 @@
+.account-dropdown-mini {
+ height: 22px;
+ background-color: $white;
+ font-family: Roboto;
+ line-height: 16px;
+ font-size: 12px;
+ width: 124px;
+
+ &__close-area {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+ }
+
+ &__list {
+ z-index: 1050;
+ position: absolute;
+ height: 180px;
+ width: 96pxpx;
+ border: 1px solid $geyser;
+ border-radius: 4px;
+ background-color: $white;
+ box-shadow: 0 3px 6px 0 rgba(0 ,0 ,0 ,.11);
+ overflow-y: scroll;
+ }
+
+ .account-list-item {
+ margin-top: 6px;
+ }
+
+ .account-list-item__account-name {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ width: 80px;
+ }
+
+ .account-list-item__top-row {
+ margin: 0;
+ }
+
+ .account-list-item__icon {
+ position: initial;
+ }
+} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss
index 4fc7c705a..c298c4019 100644
--- a/ui/app/css/itcss/components/account-dropdown.scss
+++ b/ui/app/css/itcss/components/account-dropdown.scss
@@ -62,4 +62,11 @@
&__account-secondary-balance {
color: $dusty-gray;
}
+
+ &__account-address {
+ margin-left: 35px;
+ width: 80%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
}
diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss
index 090710f7b..e40e5a8c0 100644
--- a/ui/app/css/itcss/components/account-menu.scss
+++ b/ui/app/css/itcss/components/account-menu.scss
@@ -21,6 +21,7 @@
}
&__icon {
+ margin-left: 20px;
cursor: pointer;
}
@@ -65,6 +66,8 @@
.keyring-label {
margin-top: 5px;
+ background-color: $black;
+ color: $dusty-gray;
}
}
@@ -88,9 +91,20 @@
&__check-mark {
width: 14px;
+ margin-right: 12px;
flex: 0 0 auto;
}
+ &__check-mark-icon {
+ background-image: url("images/check-white.svg");
+ height: 18px;
+ width: 18px;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+ margin: 3px 0;
+ }
+
.identicon {
margin: 0 12px 0 0;
flex: 0 0 auto;
diff --git a/ui/app/css/itcss/components/add-token.scss b/ui/app/css/itcss/components/add-token.scss
index aa8221c9a..5f6d0fcff 100644
--- a/ui/app/css/itcss/components/add-token.scss
+++ b/ui/app/css/itcss/components/add-token.scss
@@ -7,19 +7,6 @@
z-index: 12;
font-family: 'DIN Next Light';
- @media screen and (max-width: $break-small) {
- top: 0;
- width: 100%;
-
- &__wrapper {
- box-shadow: none !important;
- }
-
- &__footers {
- border-bottom: 1px solid $gallery;
- }
- }
-
&__wrapper {
background-color: $white;
box-shadow: 0 2px 4px 0 rgba($black, .08);
@@ -109,7 +96,18 @@
cursor: pointer;
&:hover {
- background-color: $gallery;
+ background-color: rgba(0, 0, 0, .05);
+ }
+
+ &:active {
+ background-color: rgba(0, 0, 0, .1);
+ }
+
+ .fa {
+ position: absolute;
+ right: 24px;
+ font-size: 24px;
+ line-height: 24px;
}
}
@@ -180,7 +178,7 @@
transition: 200ms ease-in-out;
display: flex;
flex-flow: row nowrap;
- flex: 0 0 45%;
+ flex: 0 0 42.5%;
align-items: center;
padding: 12px;
margin: 2.5%;
@@ -204,6 +202,10 @@
}
}
+ &__token-data {
+ align-self: flex-start;
+ }
+
&__token-name {
font-size: 14px;
line-height: 19px;
@@ -263,6 +265,11 @@
&__confirmation-title {
padding: 30px 120px 12px;
+
+ @media screen and (max-width: $break-small) {
+ padding: 20px 0;
+ width: 100%;
+ }
}
&__confirmation-content {
@@ -272,7 +279,7 @@
&__confirmation-token-list-item {
display: flex;
flex-flow: row nowrap;
- padding: 0 120px;
+ margin: 0 auto;
align-items: center;
}
@@ -283,4 +290,52 @@
&__confirmation-token-icon {
margin-right: 18px;
}
+
+ @media screen and (max-width: $break-small) {
+ top: 0;
+ width: 100%;
+ overflow: hidden;
+ height: 100%;
+
+ &__wrapper {
+ box-shadow: none !important;
+ flex: 1 1 auto;
+ width: 100%;
+ overflow-y: auto;
+ }
+
+ &__footers {
+ border-bottom: 1px solid $gallery;
+ }
+
+ &__token-icon {
+ width: 50px;
+ height: 50px;
+ }
+
+ &__token-symbol {
+ font-size: 18px;
+ line-height: 24px;
+ }
+
+ &__token-name {
+ font-size: 12px;
+ line-height: 16px;
+ }
+
+ &__buttons {
+ flex-flow: row nowrap;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ padding: 12px 0;
+ margin: 0;
+ border-top: 1px solid $gallery;
+
+ button {
+ flex: 1 0 auto;
+ margin: 0 12px;
+ }
+ }
+ }
}
diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss
index d4f0fe5ac..4a8232e39 100644
--- a/ui/app/css/itcss/components/confirm.scss
+++ b/ui/app/css/itcss/components/confirm.scss
@@ -16,6 +16,15 @@
}
}
+.notification {
+ .confirm-screen-wrapper {
+
+ @media screen and (max-width: $break-small) {
+ height: calc(100vh - 85px);
+ }
+ }
+}
+
.confirm-screen-wrapper {
height: 100%;
width: 380px;
@@ -37,7 +46,7 @@
overflow-y: auto;
top: 0;
box-shadow: none;
- height: calc(100vh - 58px - 100px);
+ height: calc(100vh - 58px - 85px);
border-top-left-radius: 0;
border-top-right-radius: 0;
}
@@ -66,7 +75,7 @@
flex: 0 0 auto;
@media screen and (max-width: $break-small) {
- font-size: 22px;
+ font-size: 20px;
}
}
@@ -76,8 +85,10 @@
background: $athens-grey;
position: absolute;
transform: rotate(45deg);
- left: 178px;
top: 71px;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
}
.confirm-screen-title {
@@ -133,6 +144,14 @@
height: 16px;
}
+.confirm-send-ether,
+.confirm-send-token {
+ i.fa-arrow-right {
+ align-self: start;
+ margin: 24px 14px 0 !important;
+ }
+}
+
.confirm-screen-identicons {
margin-top: 24px;
flex: 0 0 auto;
@@ -271,6 +290,7 @@ section .confirm-screen-account-number,
box-shadow: none;
flex: 1 0 auto;
font-weight: 300;
+ margin: 0 8px;
}
.btn-light.confirm-screen-cancel-button {
@@ -288,6 +308,7 @@ section .confirm-screen-account-number,
cursor: pointer;
flex: 1 0 auto;
font-weight: 300;
+ margin: 0 8px;
}
#pending-tx-form {
@@ -296,7 +317,7 @@ section .confirm-screen-account-number,
display: flex;
flex-flow: row nowrap;
background-color: $white;
- padding: 19px 18px;
+ padding: 12px 18px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
width: 100%;
diff --git a/ui/app/css/itcss/components/currency-display.scss b/ui/app/css/itcss/components/currency-display.scss
index eb1776c58..9459629b6 100644
--- a/ui/app/css/itcss/components/currency-display.scss
+++ b/ui/app/css/itcss/components/currency-display.scss
@@ -22,6 +22,7 @@
line-height: 22px;
border: none;
outline: 0 !important;
+ max-width: 100%;
}
&__primary-currency {
@@ -43,4 +44,13 @@
font-size: 12px;
line-height: 12px;
}
+
+ &__input-wrapper {
+ position: relative;
+ display: flex;
+ }
+
+ &__currency-symbol {
+ margin-top: 1px;
+ }
} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/editable-label.scss b/ui/app/css/itcss/components/editable-label.scss
new file mode 100644
index 000000000..13570610c
--- /dev/null
+++ b/ui/app/css/itcss/components/editable-label.scss
@@ -0,0 +1,34 @@
+.editable-label {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+
+ &__value {
+ max-width: 250px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ &__input {
+ width: 250px;
+ font-size: 14px;
+ text-align: center;
+
+ &--error {
+ border: 1px solid $monzo;
+ }
+ }
+
+ &__icon-wrapper {
+ position: absolute;
+ margin-left: 10px;
+ left: 100%;
+ }
+
+ &__icon {
+ cursor: pointer;
+ color: $dusty-gray;
+ }
+}
diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss
index ef84dc3f4..a6332f819 100644
--- a/ui/app/css/itcss/components/header.scss
+++ b/ui/app/css/itcss/components/header.scss
@@ -27,6 +27,10 @@
bottom: -32px;
}
}
+
+ .metafox-icon {
+ cursor: pointer;
+ }
}
.app-header-contents {
@@ -58,6 +62,7 @@
text-transform: uppercase;
font-weight: 400;
color: #22232c; // $shark
+ line-height: 29px;
@media screen and (max-width: 575px) {
display: none;
@@ -75,13 +80,13 @@ h2.page-subtitle {
display: flex;
flex-direction: row;
align-items: center;
- margin-right: 20px;
}
.left-menu-wrapper {
display: flex;
flex-direction: row;
align-items: center;
+ cursor: pointer;
}
.header__right-actions {
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index fda002785..4ba02be67 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -38,3 +38,14 @@
@import './gas-slider.scss';
+@import './settings.scss';
+
+@import './tab-bar.scss';
+
+@import './simple-dropdown.scss';
+
+@import './request-signature.scss';
+
+@import './account-dropdown-mini.scss';
+
+@import './editable-label.scss';
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index 1ffea58a9..b69bd5c7e 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.scss
@@ -57,6 +57,7 @@
border-radius: 6px;
border: 1px solid $black;
padding: 0% 7%;
+ justify-content: center;
div.buy-modal-content-option-title {
font-size: 20px;
@@ -293,6 +294,11 @@
font-size: 18px;
}
+.account-modal__name {
+ margin-top: 9px;
+ font-size: 20px;
+}
+
.private-key-password {
display: flex;
flex-direction: column;
@@ -372,6 +378,7 @@
resize: none;
padding: 9px 13px 8px;
text-transform: uppercase;
+ font-weight: 300;
}
diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss
index bb8c4eea8..98dbdffb2 100644
--- a/ui/app/css/itcss/components/network.scss
+++ b/ui/app/css/itcss/components/network.scss
@@ -1,3 +1,12 @@
+.network-component--disabled {
+ // border-color: transparent !important;
+ cursor: default;
+
+ .fa-caret-down {
+ opacity: 0;
+ }
+}
+
.network-component.pointer {
border: 1px solid $shark;
border-radius: 82px;
@@ -40,7 +49,7 @@
.dropdown-menu-item {
.menu-icon-circle,
.menu-icon-circle--active {
- margin: 0 16px;
+ margin: 0 14px;
}
}
@@ -116,8 +125,8 @@
.menu-icon-circle div,
.menu-icon-circle--active div {
- height: 17px;
- width: 17px;
+ height: 12px;
+ width: 12px;
border-radius: 17px;
}
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index 1ee8283ef..244de2ba0 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -43,8 +43,11 @@ $wallet-view-bg: $wild-sand;
.wallet-view {
display: flex;
flex-direction: column;
- flex: 33.5 0 33.5%;
+ flex: 33.5 1 33.5%;
+ width: 0;
background: $wallet-view-bg;
+ z-index: 200;
+ position: relative;
@media screen and (min-width: 576px) {
overflow-y: scroll;
@@ -52,7 +55,78 @@ $wallet-view-bg: $wild-sand;
}
.wallet-view-account-details {
- flex: 0 0 150px;
+ flex: 0 0 auto;
+ }
+
+ &__name-container {
+ flex: 0 0 auto;
+ cursor: pointer;
+ width: 100%;
+ }
+
+ &__keyring-label {
+ height: 40px;
+ color: $dusty-gray;
+ font-family: Roboto;
+ font-size: 10px;
+ line-height: 40px;
+ text-align: right;
+ padding: 0 20px;
+ }
+
+ &__details-button {
+ color: $curious-blue;
+ font-size: 10px;
+ line-height: 13px;
+ text-align: center;
+ border: 1px solid $curious-blue;
+ border-radius: 10.5px;
+ background-color: transparent;
+ margin: 0 auto;
+ padding: 4px 12px;
+ flex: 0 0 auto;
+ }
+
+ &__address {
+ border-radius: 3px;
+ background-color: $alto;
+ color: $scorpion;
+ font-size: 14px;
+ line-height: 12px;
+ padding: 4px 12px;
+ margin: 24px auto;
+ font-weight: 300;
+ cursor: pointer;
+ flex: 0 0 auto;
+ }
+
+ &__sidebar-close {
+
+ @media screen and (max-width: 575px) {
+ &::after {
+ content: '\00D7';
+ font-size: 40px;
+ color: $tundora;
+ position: absolute;
+ top: 12px;
+ left: 12px;
+ cursor: pointer;
+ }
+ }
+ }
+
+ &__add-token-button {
+ flex: 0 0 auto;
+ color: $dusty-gray;
+ font-size: 14px;
+ line-height: 19px;
+ text-align: center;
+ margin: 36px auto;
+ border: 1px solid $dusty-gray;
+ border-radius: 2px;
+ font-weight: 300;
+ background: none;
+ padding: 9px 30px;
}
}
@@ -81,7 +155,7 @@ $wallet-view-bg: $wild-sand;
background: rgb(250, 250, 250);
z-index: $sidebar-z-index;
position: fixed;
- top: 57px;
+ top: 56px;
left: 0;
right: 0;
bottom: 0;
@@ -91,7 +165,7 @@ $wallet-view-bg: $wild-sand;
overflow-y: auto;
box-shadow: rgba(0, 0, 0, .15) 2px 2px 4px;
width: 85%;
- height: calc(100% - 57px);
+ height: calc(100% - 56px);
}
.sidebar-overlay {
@@ -173,15 +247,18 @@ $wallet-view-bg: $wild-sand;
// wallet view
.account-name {
-
- @media screen and (max-width: 575px) {
- font-size: 102%;
- margin-left: 3%;
- }
-
- @media screen and (max-width: 575px) {
- text-align: center;
- }
+ font-size: 24px;
+ font-weight: 200;
+ line-height: 20px;
+ color: $scorpion;
+ margin-top: 8px;
+ margin-bottom: 24px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ width: 100%;
+ padding: 0 8px;
+ text-align: center;
}
// account options dropdown
diff --git a/ui/app/css/itcss/components/request-signature.scss b/ui/app/css/itcss/components/request-signature.scss
new file mode 100644
index 000000000..ee54235d0
--- /dev/null
+++ b/ui/app/css/itcss/components/request-signature.scss
@@ -0,0 +1,222 @@
+.request-signature {
+ &__container {
+ width: 380px;
+ border-radius: 8px;
+ background-color: $white;
+ box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
+ display: flex;
+ flex-flow: column nowrap;
+ z-index: 25;
+ align-items: center;
+ font-family: Roboto;
+ position: relative;
+ height: 100%;
+
+ @media screen and (max-width: $break-small) {
+ width: 100%;
+ top: 0;
+ box-shadow: none;
+ }
+
+ @media screen and (min-width: $break-large) {
+ max-height: 620px;
+ }
+ }
+
+ &__header {
+ height: 64px;
+ width: 100%;
+ position: relative;
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+ align-items: center;
+ flex: 0 0 auto;
+ }
+
+ &__header-background {
+ position: absolute;
+ background-color: $athens-grey;
+ z-index: 2;
+ width: 100%;
+ height: 100%;
+ }
+
+ &__header__text {
+ height: 29px;
+ width: 179px;
+ color: #5B5D67;
+ font-family: Roboto;
+ font-size: 22px;
+ font-weight: 300;
+ line-height: 29px;
+ z-index: 3;
+ }
+
+ &__header__tip-container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ }
+
+ &__header__tip {
+ height: 25px;
+ width: 25px;
+ background: $athens-grey;
+ transform: rotate(45deg);
+ position: absolute;
+ bottom: -8px;
+ z-index: 1;
+ }
+
+ &__account-info {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 18px;
+ margin-bottom: 20px;
+ }
+
+ &__account {
+ color: $dusty-gray;
+ margin-left: 17px;
+ }
+
+ &__account-text {
+ font-size: 14px;
+ }
+
+ &__balance {
+ color: $dusty-gray;
+ margin-right: 17px;
+ width: 124px;
+ }
+
+ &__balance-text {
+ text-align: right;
+ font-size: 14px;
+ }
+
+ &__balance-value {
+ text-align: right;
+ margin-top: 2.5px;
+ }
+
+ &__request-icon {
+ margin-top: 25px;
+ }
+
+ &__body {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-flow: column;
+ flex: 1 1 auto;
+ height: 0;
+ }
+
+ &__request-info {
+ display: flex;
+ justify-content: center;
+ }
+
+ &__headline {
+ height: 48px;
+ width: 240px;
+ color: $tundora;
+ font-family: Roboto;
+ font-size: 18px;
+ font-weight: 300;
+ line-height: 24px;
+ text-align: center;
+ margin-top: 20px;
+ }
+
+ &__notice {
+ color: #9B9B9B;
+ font-family: "Avenir Next";
+ font-size: 14px;
+ line-height: 19px;
+ text-align: center;
+ margin-top: 41px;
+ margin-bottom: 11px;
+ width: 100%;
+ }
+
+ &__rows {
+ height: 100%;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ border-top: 1px solid $geyser;
+ display: flex;
+ flex-flow: column;
+ }
+
+ &__row {
+ display: flex;
+ flex-flow: column;
+ }
+
+ &__row-title {
+ width: 80px;
+ color: $dusty-gray;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 22px;
+ margin-top: 12px;
+ margin-left: 18px;
+ width: 100%;
+ }
+
+ &__row-value {
+ color: $scorpion;
+ font-family: Roboto;
+ font-size: 14px;
+ line-height: 19px;
+ width: 100%;
+ overflow-wrap: break-word;
+ border-bottom: 1px solid #d2d8dd;
+ padding: 6px 18px 15px;
+ }
+
+ &__footer {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-evenly;
+ font-size: 22px;
+ position: relative;
+ flex: 0 0 auto;
+ border-top: 1px solid $geyser;
+
+ &__cancel-button,
+ &__sign-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 1 0 auto;
+ font-family: Roboto;
+ font-size: 16px;
+ font-weight: 300;
+ height: 55px;
+ line-height: 32px;
+ cursor: pointer;
+ border-radius: 2px;
+ box-shadow: none;
+ max-width: 162px;
+ margin: 12px;
+ }
+
+ &__cancel-button {
+ background: none;
+ border: 1px solid $dusty-gray;
+ margin-right: 6px;
+ }
+
+ &__sign-button {
+ background-color: $caribbean-green;
+ border-width: 0;
+ color: $white;
+ margin-left: 6px;
+ }
+ }
+} \ No newline at end of file
diff --git a/ui/app/css/itcss/components/sections.scss b/ui/app/css/itcss/components/sections.scss
index bc89fdccc..388aea175 100644
--- a/ui/app/css/itcss/components/sections.scss
+++ b/ui/app/css/itcss/components/sections.scss
@@ -39,7 +39,9 @@ textarea.twelve-word-phrase {
/* unlock */
.error {
- color: #e20202;
+ // color: #e20202;
+ color: #f7861c;
+ margin-bottom: 9px;
}
.warning {
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 9a076551e..282eef030 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -274,6 +274,7 @@
color: #9b9b9b;
font-size: .8em;
padding: 1px 4px;
+ cursor: pointer;
}
.token-gas {
@@ -400,7 +401,7 @@
.send-v2 {
&__container {
- height: 701px;
+ // height: 701px;
width: 380px;
border-radius: 8px;
background-color: $white;
@@ -416,6 +417,7 @@
width: 100%;
top: 0;
box-shadow: none;
+ flex: 1 1 auto;
}
}
@@ -473,6 +475,7 @@
@media screen and (max-width: $break-small) {
height: 59px;
+ width: 100vw;
}
}
@@ -483,10 +486,13 @@
position: absolute;
transform: rotate(45deg);
left: 178px;
- top: 65px;
+ top: 75px;
@media screen and (max-width: $break-small) {
top: 46px;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
}
}
@@ -520,18 +526,20 @@
}
&__form {
- margin-top: 13px;
+ margin: 13px 0;
width: 100%;
@media screen and (max-width: $break-small) {
- margin-top: 0px;
+ padding: 13px 0;
+ margin: 0;
height: 0;
overflow-y: auto;
flex: 1 1 auto;
}
}
- &__form-header, &__form-header-copy {
+ &__form-header,
+ &__form-header-copy {
width: 100%;
display: flex;
flex-flow: column;
@@ -594,6 +602,16 @@
}
}
+ &__to-autocomplete {
+ position: relative;
+
+ &__down-caret {
+ position: absolute;
+ top: 18px;
+ right: 12px;
+ }
+ }
+
&__to-autocomplete, &__memo-text-area {
&__input {
height: 54px;
@@ -652,31 +670,32 @@
}
&__next-btn,
- &__cancel-btn {
+ &__cancel-btn,
+ &__next-btn__disabled {
width: 163px;
text-align: center;
height: 55px;
- width: 163px;
border-radius: 2px;
background-color: $white;
font-family: Roboto;
font-size: 16px;
font-weight: 300;
line-height: 21px;
- text-align: center;
border: 1px solid;
+ margin: 0 4px;
}
+ &__next-btn,
&__next-btn__disabled {
- opacity: .5;
- cursor: auto;
- }
-
- &__next-btn {
color: $curious-blue;
border-color: $curious-blue;
}
+ &__next-btn__disabled {
+ opacity: .5;
+ cursor: auto;
+ }
+
&__cancel-btn {
color: $dusty-gray;
border-color: $dusty-gray;
@@ -692,8 +711,8 @@
flex-flow: column;
@media screen and (max-width: $break-small) {
- width: 355px;
- height: 598px;
+ width: 100vw;
+ height: 100vh;
}
&__header {
@@ -703,6 +722,10 @@
align-items: center;
justify-content: space-between;
font-size: 22px;
+
+ @media screen and (max-width: $break-small) {
+ flex: 0 0 auto;
+ }
}
&__title {
@@ -718,14 +741,19 @@
margin-right: 19.25px;
}
+ &__content {
+ display: flex;
+ flex-flow: column nowrap;
+ height: 100%;
+ }
+
&__body {
- height: 248px;
display: flex;
+ margin-bottom: 24px;
@media screen and (max-width: $break-small) {
- width: 355px;
- height: 470px;
flex-flow: column;
+ flex: 1 1 auto;
}
}
@@ -736,6 +764,11 @@
align-items: center;
justify-content: space-between;
font-size: 22px;
+ position: relative;
+
+ @media screen and (max-width: $break-small) {
+ flex: 0 0 auto;
+ }
}
&__buttons {
@@ -745,7 +778,7 @@
margin-right: 21.25px;
}
- &__revert, &__cancel, &__save {
+ &__revert, &__cancel, &__save, &__save__error {
display: flex;
justify-content: center;
align-items: center;
@@ -758,7 +791,7 @@
margin-left: 21.25px;
}
- &__cancel, &__save {
+ &__cancel, &__save, &__save__error {
height: 34.64px;
width: 85.74px;
border: 1px solid $dusty-gray;
@@ -767,6 +800,21 @@
font-size: 12px;
color: $dusty-gray;
}
+
+ &__save__error {
+ opacity: 0.5;
+ cursor: auto;
+ }
+
+ &__error-message {
+ display: block;
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ font-size: 12px;
+ line-height: 12px;
+ color: $red;
+ }
}
&__gas-modal-card {
@@ -778,7 +826,6 @@
&__title {
height: 26px;
- width: 84px;
color: $tundora;
font-family: Roboto;
font-size: 20px;
diff --git a/ui/app/css/itcss/components/settings.scss b/ui/app/css/itcss/components/settings.scss
new file mode 100644
index 000000000..2f29d8017
--- /dev/null
+++ b/ui/app/css/itcss/components/settings.scss
@@ -0,0 +1,201 @@
+.settings {
+ position: relative;
+ background: $white;
+ display: flex;
+ flex-flow: column nowrap;
+ height: auto;
+ overflow: auto;
+}
+
+.settings__header {
+ padding: 25px;
+}
+
+.settings__close-button::after {
+ content: '\00D7';
+ font-size: 40px;
+ color: $dusty-gray;
+ position: absolute;
+ top: 25px;
+ right: 30px;
+ cursor: pointer;
+}
+
+.settings__error {
+ padding-bottom: 20px;
+ text-align: center;
+ color: $crimson;
+}
+
+.settings__content {
+ padding: 0 25px;
+}
+
+.settings__content-row {
+ display: flex;
+ flex-direction: row;
+ padding: 10px 0 20px;
+
+ @media screen and (max-width: 575px) {
+ flex-direction: column;
+ padding: 10px 0;
+ }
+}
+
+.settings__content-item {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ padding: 0 5px;
+ height: 71px;
+
+ @media screen and (max-width: 575px) {
+ height: initial;
+ padding: 5px 0;
+ }
+
+ &--without-height {
+ height: initial;
+ }
+}
+
+.settings__content-item-col {
+ max-width: 300px;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: 575px) {
+ max-width: 100%;
+ width: 100%;
+ }
+}
+
+.settings__content-description {
+ font-size: 14px;
+ color: $dusty-gray;
+ padding-top: 5px;
+}
+
+.settings__input {
+ padding-left: 10px;
+ font-size: 14px;
+ height: 40px;
+ border: 1px solid $alto;
+}
+
+.settings__input::-webkit-input-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__input::-moz-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__input:-ms-input-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__input:-moz-placeholder {
+ font-weight: 100;
+ color: $dusty-gray;
+}
+
+.settings__provider-wrapper {
+ font-size: 16px;
+ border: 1px solid $alto;
+ border-radius: 2px;
+ padding: 15px;
+ background-color: $white;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+}
+
+.settings__provider-icon {
+ height: 10px;
+ width: 10px;
+ margin-right: 10px;
+ border-radius: 10px;
+}
+
+.settings__rpc-save-button {
+ align-self: flex-end;
+ padding: 5px;
+ text-transform: uppercase;
+ color: $dusty-gray;
+ cursor: pointer;
+}
+
+.settings__clear-button {
+ font-size: 16px;
+ border: 1px solid $curious-blue;
+ color: $curious-blue;
+ border-radius: 2px;
+ padding: 18px;
+ background-color: $white;
+ text-transform: uppercase;
+}
+
+.settings__clear-button--red {
+ border: 1px solid $monzo;
+ color: $monzo;
+}
+
+.settings__info-logo-wrapper {
+ height: 80px;
+ margin-bottom: 20px;
+}
+
+.settings__info-logo {
+ max-height: 100%;
+ max-width: 100%;
+}
+
+.settings__info-item {
+ padding: 10px 0;
+}
+
+.settings__info-link-header {
+ padding-bottom: 15px;
+
+ @media screen and (max-width: 575px) {
+ padding-bottom: 5px;
+ }
+}
+
+.settings__info-link-item {
+ padding: 15px 0;
+
+ @media screen and (max-width: 575px) {
+ padding: 5px 0;
+ }
+}
+
+.settings__info-version-number {
+ padding-top: 5px;
+ font-size: 13px;
+ color: $dusty-gray;
+}
+
+.settings__info-about {
+ color: $dusty-gray;
+ margin-bottom: 15px;
+}
+
+.settings__info-link {
+ color: $curious-blue;
+}
+
+.settings__info-separator {
+ margin: 15px 0;
+ width: 80px;
+ border-color: $alto;
+ border: none;
+ height: 1px;
+ background-color: $alto;
+ color: $alto;
+}
diff --git a/ui/app/css/itcss/components/simple-dropdown.scss b/ui/app/css/itcss/components/simple-dropdown.scss
new file mode 100644
index 000000000..a21095a3e
--- /dev/null
+++ b/ui/app/css/itcss/components/simple-dropdown.scss
@@ -0,0 +1,65 @@
+.simple-dropdown {
+ height: 56px;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ border: 1px solid $alto;
+ border-radius: 4px;
+ background-color: $white;
+ font-size: 16px;
+ color: #4d4d4d;
+ cursor: pointer;
+ position: relative;
+}
+
+.simple-dropdown__caret {
+ color: $silver;
+ padding: 0 10px;
+}
+
+.simple-dropdown__selected {
+ flex-grow: 1;
+ padding: 0 15px;
+}
+
+.simple-dropdown__options {
+ z-index: 1050;
+ position: absolute;
+ height: 220px;
+ width: 100%;
+ border: 1px solid #d2d8dd;
+ border-radius: 4px;
+ background-color: #fff;
+ -webkit-box-shadow: 0 3px 6px 0 rgba(0, 0, 0, .11);
+ box-shadow: 0 3px 6px 0 rgba(0, 0, 0, .11);
+ margin-top: 10px;
+ overflow-y: scroll;
+ left: 0;
+ top: 100%;
+}
+
+.simple-dropdown__option {
+ padding: 10px;
+
+ &:hover {
+ background-color: $gallery;
+ }
+}
+
+.simple-dropdown__option--selected {
+ background-color: $alto;
+
+ &:hover {
+ background-color: $alto;
+ cursor: default;
+ }
+}
+
+.simple-dropdown__close-area {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+}
diff --git a/ui/app/css/itcss/components/tab-bar.scss b/ui/app/css/itcss/components/tab-bar.scss
new file mode 100644
index 000000000..4f3077974
--- /dev/null
+++ b/ui/app/css/itcss/components/tab-bar.scss
@@ -0,0 +1,23 @@
+.tab-bar {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: flex-end;
+}
+
+.tab-bar__tab {
+ min-width: 0;
+ flex: 0 0 auto;
+ padding: 15px 25px;
+ border-bottom: 1px solid $alto;
+ box-sizing: border-box;
+ font-size: 18px;
+}
+
+.tab-bar__tab--active {
+ border-color: $black;
+}
+
+.tab-bar__grow-tab {
+ flex-grow: 1;
+}
diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss
index bbc64c324..d4add71b1 100644
--- a/ui/app/css/itcss/components/token-list.scss
+++ b/ui/app/css/itcss/components/token-list.scss
@@ -67,19 +67,21 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
position: fixed;
margin-top: 20px;
margin-left: 105px;
+ z-index: 2000;
&__close-area {
position: fixed;
top: 0;
left: 0;
- z-index: 1000;
+ z-index: 2100;
width: 100%;
height: 100%;
+ cursor: default;
}
&__container {
padding: 16px 34px 32px;
- z-index: 1050;
+ z-index: 2200;
position: relative;
}
diff --git a/ui/app/css/itcss/components/wallet-balance.scss b/ui/app/css/itcss/components/wallet-balance.scss
index cd44f89bb..64b291b89 100644
--- a/ui/app/css/itcss/components/wallet-balance.scss
+++ b/ui/app/css/itcss/components/wallet-balance.scss
@@ -1,4 +1,4 @@
-$wallet-balance-bg: $gallery;
+$wallet-balance-bg: #e7e7e7;
$wallet-balance-breakpoint: 890px;
$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
@@ -20,6 +20,7 @@ $wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (
align-items: center;
flex: 0 0 auto;
cursor: pointer;
+ border-top: 1px solid $wallet-balance-bg;
.balance-container {
display: flex;
diff --git a/ui/app/css/itcss/tools/utilities.scss b/ui/app/css/itcss/tools/utilities.scss
index ca9fd0d9c..ee867640d 100644
--- a/ui/app/css/itcss/tools/utilities.scss
+++ b/ui/app/css/itcss/tools/utilities.scss
@@ -237,7 +237,6 @@ hr.horizontal-line {
color: #fff;
border-radius: 10px;
padding: 4px;
- width: 41px;
text-align: center;
height: 15px;
}
diff --git a/ui/app/keychains/hd/create-vault-complete.js b/ui/app/keychains/hd/create-vault-complete.js
index 745990351..5ab5d4c33 100644
--- a/ui/app/keychains/hd/create-vault-complete.js
+++ b/ui/app/keychains/hd/create-vault-complete.js
@@ -62,7 +62,8 @@ CreateVaultCompleteScreen.prototype.render = function () {
}),
h('button.primary', {
- onClick: () => this.confirmSeedWords(),
+ onClick: () => this.confirmSeedWords()
+ .then(account => this.showAccountDetail(account)),
style: {
margin: '24px',
fontSize: '0.9em',
@@ -82,5 +83,9 @@ CreateVaultCompleteScreen.prototype.render = function () {
}
CreateVaultCompleteScreen.prototype.confirmSeedWords = function () {
- this.props.dispatch(actions.confirmSeedWords())
+ return this.props.dispatch(actions.confirmSeedWords())
+}
+
+CreateVaultCompleteScreen.prototype.showAccountDetail = function (account) {
+ return this.props.dispatch(actions.showAccountDetail(account))
}
diff --git a/ui/app/main-container.js b/ui/app/main-container.js
index eaaff8517..6e2342c2b 100644
--- a/ui/app/main-container.js
+++ b/ui/app/main-container.js
@@ -3,7 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountAndTransactionDetails = require('./account-and-transaction-details')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
-const ConfigScreen = require('./config')
+const Settings = require('./settings')
const UnlockScreen = require('./unlock')
module.exports = MainContainer
@@ -38,7 +38,7 @@ MainContainer.prototype.render = function () {
case 'config':
log.debug('rendering config screen from unlock screen.')
contents = {
- component: ConfigScreen,
+ component: Settings,
key: 'config',
}
break
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index 1cded7ca7..e1a890535 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -42,7 +42,7 @@ function rootReducer (state, action) {
}
window.logState = function () {
- let state = window.METAMASK_CACHED_LOG_STATE
+ const state = window.METAMASK_CACHED_LOG_STATE
let version
try {
version = global.platform.getVersion()
@@ -50,7 +50,7 @@ window.logState = function () {
version = 'unable to load version.'
}
state.version = version
- let stateString = JSON.stringify(state, removeSeedWords, 2)
+ const stateString = JSON.stringify(state, removeSeedWords, 2)
return stateString
}
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index f10bf9fb7..6fb7f8cca 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -557,6 +557,16 @@ function reduceApp (state, action) {
},
})
+ case actions.ONBOARDING_BUY_ETH_VIEW:
+ return extend(appState, {
+ transForward: true,
+ currentView: {
+ name: 'onboardingBuyEth',
+ context: appState.currentView.name,
+ },
+ identity: state.metamask.identities[action.value],
+ })
+
case actions.COINBASE_SUBVIEW:
return extend(appState, {
buyView: {
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index fb2b2e674..7408f827a 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -1,5 +1,6 @@
const extend = require('xtend')
const actions = require('../actions')
+const MetamascaraPlatform = require('../../../app/scripts/platforms/window')
module.exports = reduceMetamask
@@ -11,6 +12,7 @@ function reduceMetamask (state, action) {
isInitialized: false,
isUnlocked: false,
isAccountMenuOpen: false,
+ isMascara: window.platform instanceof MetamascaraPlatform,
rpcTarget: 'https://rawtestrpc.metamask.io/',
identities: {},
unapprovedTxs: {},
@@ -31,6 +33,7 @@ function reduceMetamask (state, action) {
memo: '',
errors: {},
},
+ coinOptions: {},
}, state.metamask)
switch (action.type) {
@@ -150,7 +153,7 @@ function reduceMetamask (state, action) {
})
case actions.UPDATE_TOKEN_EXCHANGE_RATE:
- const { payload: { pair, marketinfo } } = action
+ const { payload: { pair, marketinfo } } = action
return extend(metamaskState, {
tokenExchangeRates: {
...metamaskState.tokenExchangeRates,
@@ -226,10 +229,6 @@ function reduceMetamask (state, action) {
})
case actions.UPDATE_SEND_ERRORS:
- console.log(123, {
- ...metamaskState.send.errors,
- ...action.value,
- })
return extend(metamaskState, {
send: {
...metamaskState.send,
@@ -240,6 +239,39 @@ function reduceMetamask (state, action) {
},
})
+ case actions.CLEAR_SEND:
+ return extend(metamaskState, {
+ send: {
+ gasLimit: null,
+ gasPrice: null,
+ gasTotal: null,
+ from: '',
+ to: '',
+ amount: '0x0',
+ memo: '',
+ errors: {},
+ },
+ })
+
+ case actions.PAIR_UPDATE:
+ const { value: { marketinfo: pairMarketInfo } } = action
+ return extend(metamaskState, {
+ tokenExchangeRates: {
+ ...metamaskState.tokenExchangeRates,
+ [pairMarketInfo.pair]: pairMarketInfo,
+ },
+ })
+
+ case actions.SHAPESHIFT_SUBVIEW:
+ const { value: { marketinfo: ssMarketInfo, coinOptions } } = action
+ return extend(metamaskState, {
+ tokenExchangeRates: {
+ ...metamaskState.tokenExchangeRates,
+ [marketinfo.pair]: ssMarketInfo,
+ },
+ coinOptions,
+ })
+
default:
return metamaskState
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
index 4c3d21d33..3a15cef4c 100644
--- a/ui/app/selectors.js
+++ b/ui/app/selectors.js
@@ -1,5 +1,9 @@
const valuesFor = require('./util').valuesFor
+const {
+ multiplyCurrencies,
+} = require('./conversion-util')
+
const selectors = {
getSelectedAddress,
getSelectedIdentity,
@@ -16,6 +20,8 @@ const selectors = {
getAddressBook,
getSendFrom,
getCurrentCurrency,
+ getSendAmount,
+ getSelectedTokenToFiatRate,
}
module.exports = selectors
@@ -123,6 +129,23 @@ function getSendFrom (state) {
return state.metamask.send.from
}
+function getSendAmount (state) {
+ return state.metamask.send.amount
+}
+
function getCurrentCurrency (state) {
return state.metamask.currentCurrency
}
+
+function getSelectedTokenToFiatRate (state) {
+ const selectedTokenExchangeRate = getSelectedTokenExchangeRate(state)
+ const conversionRate = conversionRateSelector(state)
+
+ const tokenToFiatRate = multiplyCurrencies(
+ conversionRate,
+ selectedTokenExchangeRate,
+ { toNumericBase: 'dec' }
+ )
+
+ return tokenToFiatRate
+}
diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js
index 5e64daceb..e772477ae 100644
--- a/ui/app/send-v2.js
+++ b/ui/app/send-v2.js
@@ -2,6 +2,7 @@ const { inherits } = require('util')
const PersistentForm = require('../lib/persistent-form')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
+const classnames = require('classnames')
const Identicon = require('./components/identicon')
const FromDropdown = require('./components/send/from-dropdown')
@@ -19,6 +20,9 @@ const {
conversionGreaterThan,
addCurrencies,
} = require('./conversion-util')
+const {
+ isBalanceSufficient,
+} = require('./components/send/send-utils.js')
const { isValidAddress } = require('./util')
module.exports = SendTransactionScreen
@@ -28,7 +32,8 @@ function SendTransactionScreen () {
PersistentForm.call(this)
this.state = {
- dropdownOpen: false,
+ fromDropdownOpen: false,
+ toDropdownOpen: false,
errors: {
to: null,
amount: null,
@@ -153,7 +158,7 @@ SendTransactionScreen.prototype.renderFromRow = function () {
updateSendFrom,
} = this.props
- const { dropdownOpen } = this.state
+ const { fromDropdownOpen } = this.state
return h('div.send-v2__form-row', [
@@ -161,12 +166,12 @@ SendTransactionScreen.prototype.renderFromRow = function () {
h('div.send-v2__form-field', [
h(FromDropdown, {
- dropdownOpen,
+ dropdownOpen: fromDropdownOpen,
accounts: fromAccounts,
selectedAccount: from,
onSelect: updateSendFrom,
- openDropdown: () => this.setState({ dropdownOpen: true }),
- closeDropdown: () => this.setState({ dropdownOpen: false }),
+ openDropdown: () => this.setState({ fromDropdownOpen: true }),
+ closeDropdown: () => this.setState({ fromDropdownOpen: false }),
conversionRate,
}),
]),
@@ -174,15 +179,20 @@ SendTransactionScreen.prototype.renderFromRow = function () {
])
}
-SendTransactionScreen.prototype.handleToChange = function (event) {
- const { updateSendTo, updateSendErrors } = this.props
- const to = event.target.value
+SendTransactionScreen.prototype.handleToChange = function (to) {
+ const {
+ updateSendTo,
+ updateSendErrors,
+ from: {address: from},
+ } = this.props
let toError = null
if (!to) {
toError = 'Required'
} else if (!isValidAddress(to)) {
- toError = 'Recipient address is invalid.'
+ toError = 'Recipient address is invalid'
+ } else if (to === from) {
+ toError = 'From and To address cannot be the same'
}
updateSendTo(to)
@@ -190,8 +200,9 @@ SendTransactionScreen.prototype.handleToChange = function (event) {
}
SendTransactionScreen.prototype.renderToRow = function () {
- const { toAccounts, errors } = this.props
- const { to } = this.state
+ const { toAccounts, errors, to } = this.props
+
+ const { toDropdownOpen } = this.state
return h('div.send-v2__form-row', [
@@ -206,7 +217,10 @@ SendTransactionScreen.prototype.renderToRow = function () {
h('div.send-v2__form-field', [
h(ToAutoComplete, {
to,
- accounts: toAccounts,
+ accounts: Object.entries(toAccounts).map(([key, account]) => account),
+ dropdownOpen: toDropdownOpen,
+ openDropdown: () => this.setState({ toDropdownOpen: true }),
+ closeDropdown: () => this.setState({ toDropdownOpen: false }),
onChange: this.handleToChange,
inError: Boolean(errors.to),
}),
@@ -237,28 +251,16 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
let amountError = null
- const totalAmount = addCurrencies(amount, gasTotal, {
- aBase: 16,
- bBase: 16,
- toNumericBase: 'hex',
+ const sufficientBalance = isBalanceSufficient({
+ amount,
+ gasTotal,
+ balance,
+ primaryCurrency,
+ selectedToken,
+ amountConversionRate,
+ conversionRate,
})
- const sufficientBalance = conversionGreaterThan(
- {
- value: balance,
- fromNumericBase: 'hex',
- fromCurrency: primaryCurrency,
- conversionRate,
- },
- {
- value: totalAmount,
- fromNumericBase: 'hex',
- conversionRate: amountConversionRate,
- fromCurrency: selectedToken || primaryCurrency,
- conversionRate: amountConversionRate,
- },
- )
-
const amountLessThanZero = conversionGreaterThan(
{ value: 0, fromNumericBase: 'dec' },
{ value: amount, fromNumericBase: 'hex' },
@@ -376,19 +378,29 @@ SendTransactionScreen.prototype.renderForm = function () {
this.renderGasRow(),
- this.renderMemoRow(),
+ // this.renderMemoRow(),
])
}
SendTransactionScreen.prototype.renderFooter = function () {
- const { goHome } = this.props
+ const {
+ goHome,
+ clearSend,
+ errors: { amount: amountError, to: toError },
+ } = this.props
+
+ const noErrors = amountError === null && toError === null
+ const errorClass = noErrors ? '' : '__disabled'
return h('div.send-v2__footer', [
h('button.send-v2__cancel-btn', {
- onClick: goHome,
+ onClick: () => {
+ clearSend()
+ goHome()
+ },
}, 'Cancel'),
- h('button.send-v2__next-btn', {
+ h(`button.send-v2__next-btn${errorClass}`, {
onClick: event => this.onSubmit(event),
}, 'Next'),
])
@@ -429,8 +441,16 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
signTx,
selectedToken,
toAccounts,
+ clearSend,
+ errors: { amount: amountError, to: toError },
} = this.props
+ const noErrors = amountError === null && toError === null
+
+ if (!noErrors) {
+ return
+ }
+
this.addToAddressBookIfNew(to)
const txParams = {
@@ -445,6 +465,8 @@ SendTransactionScreen.prototype.onSubmit = function (event) {
txParams.to = to
}
+ clearSend()
+
selectedToken
? signTokenTx(selectedToken.address, to, amount, txParams)
: signTx(txParams)
diff --git a/ui/app/settings.js b/ui/app/settings.js
index 454cc95e0..786a70e7e 100644
--- a/ui/app/settings.js
+++ b/ui/app/settings.js
@@ -1,59 +1,363 @@
-const inherits = require('util').inherits
-const Component = require('react').Component
+const { Component } = require('react')
+const PropTypes = require('prop-types')
const h = require('react-hyperscript')
-const connect = require('react-redux').connect
+const { connect } = require('react-redux')
const actions = require('./actions')
+const infuraCurrencies = require('./infura-conversion.json')
+const validUrl = require('valid-url')
+const { exportAsFile } = require('./util')
+const TabBar = require('./components/tab-bar')
+const SimpleDropdown = require('./components/dropdowns/simple-dropdown')
-module.exports = connect(mapStateToProps)(AppSettingsPage)
+const getInfuraCurrencyOptions = () => {
+ const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
+ return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase())
+ })
-function mapStateToProps (state) {
- return {}
+ return sortedCurrencies.map(({ quote: { code, name } }) => {
+ return {
+ displayValue: `${code.toUpperCase()} - ${name}`,
+ key: code,
+ value: code,
+ }
+ })
}
-inherits(AppSettingsPage, Component)
-function AppSettingsPage () {
- Component.call(this)
-}
+class Settings extends Component {
+ constructor (props) {
+ super(props)
-AppSettingsPage.prototype.render = function () {
- return (
+ const { tab } = props
+ const activeTab = tab === 'info' ? 'info' : 'settings'
- h('.account-detail-section.flex-column.flex-grow', [
+ this.state = {
+ activeTab,
+ newRpc: '',
+ }
+ }
- // subtitle and nav
- h('.flex-row.flex-center', [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- onClick: this.navigateToAccounts.bind(this),
- }),
- h('h2.page-subtitle', 'Settings'),
- ]),
+ renderTabs () {
+ const { activeTab } = this.state
- h('label', {
- htmlFor: 'settings-rpc-endpoint',
- }, 'RPC Endpoint:'),
- h('input', {
- type: 'url',
- id: 'settings-rpc-endpoint',
- onKeyPress: this.onKeyPress.bind(this),
+ return h('div.settings__tabs', [
+ h(TabBar, {
+ tabs: [
+ { content: 'Settings', key: 'settings' },
+ { content: 'Info', key: 'info' },
+ ],
+ defaultTab: activeTab,
+ tabSelected: key => this.setState({ activeTab: key }),
}),
+ ])
+ }
+
+ renderCurrentConversion () {
+ const { metamask: { currentCurrency, conversionDate }, setCurrentCurrency } = this.props
+ return h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('span', 'Current Conversion'),
+ h('span.settings__content-description', `Updated ${Date(conversionDate)}`),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h(SimpleDropdown, {
+ placeholder: 'Select Currency',
+ options: getInfuraCurrencyOptions(),
+ selectedOption: currentCurrency,
+ onSelect: newCurrency => setCurrentCurrency(newCurrency),
+ }),
+ ]),
+ ]),
])
+ }
+
+ renderCurrentProvider () {
+ const { metamask: { provider = {} } } = this.props
+ let title, value, color
+
+ switch (provider.type) {
+
+ case 'mainnet':
+ title = 'Current Network'
+ value = 'Main Ethereum Network'
+ color = '#038789'
+ break
+
+ case 'ropsten':
+ title = 'Current Network'
+ value = 'Ropsten Test Network'
+ color = '#e91550'
+ break
+
+ case 'kovan':
+ title = 'Current Network'
+ value = 'Kovan Test Network'
+ color = '#690496'
+ break
+
+ case 'rinkeby':
+ title = 'Current Network'
+ value = 'Rinkeby Test Network'
+ color = '#ebb33f'
+ break
+
+ default:
+ title = 'Current RPC'
+ value = provider.rpcTarget
+ }
+
+ return h('div.settings__content-row', [
+ h('div.settings__content-item', title),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('div.settings__provider-wrapper', [
+ h('div.settings__provider-icon', { style: { background: color } }),
+ h('div', value),
+ ]),
+ ]),
+ ]),
+ ])
+ }
+
+ renderNewRpcUrl () {
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('span', 'New RPC URL'),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('input.settings__input', {
+ placeholder: 'New RPC URL',
+ onChange: event => this.setState({ newRpc: event.target.value }),
+ onKeyPress: event => {
+ if (event.key === 'Enter') {
+ this.validateRpc(this.state.newRpc)
+ }
+ },
+ }),
+ h('div.settings__rpc-save-button', {
+ onClick: event => {
+ event.preventDefault()
+ this.validateRpc(this.state.newRpc)
+ },
+ }, 'Save'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ validateRpc (newRpc) {
+ const { setRpcTarget, displayWarning } = this.props
- )
+ if (validUrl.isWebUri(newRpc)) {
+ setRpcTarget(newRpc)
+ } else {
+ const appendedRpc = `http://${newRpc}`
+
+ if (validUrl.isWebUri(appendedRpc)) {
+ displayWarning('URIs require the appropriate HTTP/HTTPS prefix.')
+ } else {
+ displayWarning('Invalid RPC URI')
+ }
+ }
+ }
+
+ renderStateLogs () {
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', [
+ h('div', 'State Logs'),
+ h(
+ 'div.settings__content-description',
+ 'State logs contain your public account addresses and sent transactions.'
+ ),
+ ]),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('button.settings__clear-button', {
+ onClick (event) {
+ exportAsFile('MetaMask State Logs', window.logState())
+ },
+ }, 'Download State Logs'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ renderSeedWords () {
+ const { revealSeedConfirmation } = this.props
+
+ return (
+ h('div.settings__content-row', [
+ h('div.settings__content-item', 'Reveal Seed Words'),
+ h('div.settings__content-item', [
+ h('div.settings__content-item-col', [
+ h('button.settings__clear-button.settings__clear-button--red', {
+ onClick (event) {
+ event.preventDefault()
+ revealSeedConfirmation()
+ },
+ }, 'Reveal Seed Words'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ renderSettingsContent () {
+ const { warning } = this.props
+
+ return (
+ h('div.settings__content', [
+ warning && h('div.settings__error', warning),
+ this.renderCurrentConversion(),
+ // this.renderCurrentProvider(),
+ this.renderNewRpcUrl(),
+ this.renderStateLogs(),
+ this.renderSeedWords(),
+ ])
+ )
+ }
+
+ renderLogo () {
+ return (
+ h('div.settings__info-logo-wrapper', [
+ h('img.settings__info-logo', { src: 'images/info-logo.png' }),
+ ])
+ )
+ }
+
+ renderInfoLinks () {
+ return (
+ h('div.settings__content-item.settings__content-item--without-height', [
+ h('div.settings__info-link-header', 'Links'),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/privacy.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Privacy Policy'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/terms.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Terms of Use'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/attributions.html',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Attributions'),
+ ]),
+ ]),
+ h('hr.settings__info-separator'),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://support.metamask.io',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Visit our Support Center'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ href: 'https://metamask.io/',
+ target: '_blank',
+ }, [
+ h('span.settings__info-link', 'Visit our web site'),
+ ]),
+ ]),
+ h('div.settings__info-link-item', [
+ h('a', {
+ target: '_blank',
+ href: 'mailto:help@metamask.io?subject=Feedback',
+ }, [
+ h('span.settings__info-link', 'Email us!'),
+ ]),
+ ]),
+ ])
+ )
+ }
+
+ renderInfoContent () {
+ return (
+ h('div.settings__content', [
+ h('div.settings__content-row', [
+ h('div.settings__content-item.settings__content-item--without-height', [
+ this.renderLogo(),
+ h('div.settings__info-item', [
+ h('div.settings__info-version-header', 'MetaMask Version'),
+ h('div.settings__info-version-number', '4.0.0'),
+ ]),
+ h('div.settings__info-item', [
+ h(
+ 'div.settings__info-about',
+ 'MetaMask is designed and built in California.'
+ ),
+ ]),
+ ]),
+ this.renderInfoLinks(),
+ ]),
+ ])
+ )
+ }
+
+ render () {
+ const { goHome } = this.props
+ const { activeTab } = this.state
+
+ return (
+ h('.main-container.settings', {}, [
+ h('.settings__header', [
+ h('div.settings__close-button', {
+ onClick: goHome,
+ }),
+ this.renderTabs(),
+ ]),
+
+ activeTab === 'settings'
+ ? this.renderSettingsContent()
+ : this.renderInfoContent(),
+ ])
+ )
+ }
}
-AppSettingsPage.prototype.componentDidMount = function () {
- document.querySelector('input').focus()
+Settings.propTypes = {
+ tab: PropTypes.string,
+ metamask: PropTypes.object,
+ setCurrentCurrency: PropTypes.func,
+ setRpcTarget: PropTypes.func,
+ displayWarning: PropTypes.func,
+ revealSeedConfirmation: PropTypes.func,
+ warning: PropTypes.string,
+ goHome: PropTypes.func,
}
-AppSettingsPage.prototype.onKeyPress = function (event) {
- // get submit event
- if (event.key === 'Enter') {
- // this.submitPassword(event)
+const mapStateToProps = state => {
+ return {
+ metamask: state.metamask,
+ warning: state.appState.warning,
}
}
-AppSettingsPage.prototype.navigateToAccounts = function (event) {
- event.stopPropagation()
- this.props.dispatch(actions.showAccountsPage())
+const mapDispatchToProps = dispatch => {
+ return {
+ goHome: () => dispatch(actions.goHome()),
+ setCurrentCurrency: currency => dispatch(actions.setCurrentCurrency(currency)),
+ setRpcTarget: newRpc => dispatch(actions.setRpcTarget(newRpc)),
+ displayWarning: warning => dispatch(actions.displayWarning(warning)),
+ revealSeedConfirmation: () => dispatch(actions.revealSeedConfirmation()),
+ }
}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings)
diff --git a/ui/css.js b/ui/css.js
index d8f954434..0d0f60806 100644
--- a/ui/css.js
+++ b/ui/css.js
@@ -5,6 +5,7 @@ module.exports = bundleCss
var cssFiles = {
'index.css': fs.readFileSync(path.join(__dirname, '/app/css/output/index.css'), 'utf8'),
+ 'first-time.css': fs.readFileSync(path.join(__dirname, '../mascara/src/app/first-time/index.css'), 'utf8'),
'react-tooltip-component.css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-tooltip-component', 'dist', 'react-tooltip-component.css'), 'utf8'),
'react-css': fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'react-select', 'dist', 'react-select.css'), 'utf8'),
}
diff --git a/ui/lib/tx-helper.js b/ui/lib/tx-helper.js
index 341567e2f..de3f00d2d 100644
--- a/ui/lib/tx-helper.js
+++ b/ui/lib/tx-helper.js
@@ -24,4 +24,4 @@ module.exports = function (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMes
})
return allValues
-} \ No newline at end of file
+}