aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/account-and-transaction-details.js38
-rw-r--r--ui/app/account-detail.js183
-rw-r--r--ui/app/actions.js147
-rw-r--r--ui/app/add-token.js1
-rw-r--r--ui/app/app.js507
-rw-r--r--ui/app/components/balance-component.js120
-rw-r--r--ui/app/components/buy-button-subview.js2
-rw-r--r--ui/app/components/dropdowns/account-options-dropdown.js29
-rw-r--r--ui/app/components/dropdowns/account-selection-dropdown.js29
-rw-r--r--ui/app/components/dropdowns/components/account-dropdowns.js (renamed from ui/app/components/account-dropdowns.js)280
-rw-r--r--ui/app/components/dropdowns/components/dropdown.js (renamed from ui/app/components/dropdown.js)26
-rw-r--r--ui/app/components/dropdowns/components/network-dropdown-icon.js28
-rw-r--r--ui/app/components/dropdowns/index.js17
-rw-r--r--ui/app/components/dropdowns/network-dropdown.js315
-rw-r--r--ui/app/components/ens-input.js4
-rw-r--r--ui/app/components/eth-balance.js107
-rw-r--r--ui/app/components/fiat-value.js20
-rw-r--r--ui/app/components/identicon.js42
-rw-r--r--ui/app/components/input-number.js57
-rw-r--r--ui/app/components/menu-droppo.js6
-rw-r--r--ui/app/components/modals/account-details-modal.js82
-rw-r--r--ui/app/components/modals/buy-options-modal.js77
-rw-r--r--ui/app/components/modals/edit-account-name-modal.js76
-rw-r--r--ui/app/components/modals/index.js5
-rw-r--r--ui/app/components/modals/modal.js162
-rw-r--r--ui/app/components/modals/new-account-modal.js75
-rw-r--r--ui/app/components/network.js23
-rw-r--r--ui/app/components/pending-tx.js803
-rw-r--r--ui/app/components/qr-code.js31
-rw-r--r--ui/app/components/send-token/index.js365
-rw-r--r--ui/app/components/send/currency-toggle.js44
-rw-r--r--ui/app/components/send/eth-fee-display.js37
-rw-r--r--ui/app/components/send/gas-fee-display.js62
-rw-r--r--ui/app/components/send/gas-tooltip.js100
-rw-r--r--ui/app/components/send/usd-fee-display.js35
-rw-r--r--ui/app/components/shapeshift-form.js4
-rw-r--r--ui/app/components/token-balance.js108
-rw-r--r--ui/app/components/token-cell.js82
-rw-r--r--ui/app/components/token-list.js146
-rw-r--r--ui/app/components/transaction-list-item.js2
-rw-r--r--ui/app/components/tx-list-item.js187
-rw-r--r--ui/app/components/tx-list.js117
-rw-r--r--ui/app/components/tx-view.js148
-rw-r--r--ui/app/components/wallet-content-display.js56
-rw-r--r--ui/app/components/wallet-view.js170
-rw-r--r--ui/app/conf-tx.js124
-rw-r--r--ui/app/conversion-util.js119
-rw-r--r--ui/app/css/debug.css21
-rw-r--r--ui/app/css/fonts.css36
-rw-r--r--ui/app/css/index.scss13
-rw-r--r--ui/app/css/itcss/base/index.scss1
-rw-r--r--ui/app/css/itcss/components/account-dropdown.scss17
-rw-r--r--ui/app/css/itcss/components/buttons.scss102
-rw-r--r--ui/app/css/itcss/components/confirm.scss257
-rw-r--r--ui/app/css/itcss/components/footer.scss4
-rw-r--r--ui/app/css/itcss/components/header.scss71
-rw-r--r--ui/app/css/itcss/components/hero-balance.scss110
-rw-r--r--ui/app/css/itcss/components/index.scss29
-rw-r--r--ui/app/css/itcss/components/modal.scss363
-rw-r--r--ui/app/css/itcss/components/network.scss110
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss179
-rw-r--r--ui/app/css/itcss/components/sections.scss (renamed from ui/app/css/index.css)394
-rw-r--r--ui/app/css/itcss/components/send.scss374
-rw-r--r--ui/app/css/itcss/components/token-list.scss46
-rw-r--r--ui/app/css/itcss/components/transaction-list.scss257
-rw-r--r--ui/app/css/itcss/components/wallet-balance.scss70
-rw-r--r--ui/app/css/itcss/generic/index.scss71
-rw-r--r--ui/app/css/itcss/generic/reset.scss147
-rw-r--r--ui/app/css/itcss/objects/index.scss1
-rw-r--r--ui/app/css/itcss/settings/index.scss3
-rw-r--r--ui/app/css/itcss/settings/typography.scss71
-rw-r--r--ui/app/css/itcss/settings/variables.scss69
-rw-r--r--ui/app/css/itcss/tools/index.scss1
-rw-r--r--ui/app/css/itcss/tools/utilities.scss (renamed from ui/app/css/lib.css)93
-rw-r--r--ui/app/css/itcss/trumps/index.scss (renamed from ui/app/css/transitions.css)40
-rw-r--r--ui/app/css/reset.css48
-rw-r--r--ui/app/main-container.js71
-rw-r--r--ui/app/reducers/app.js71
-rw-r--r--ui/app/reducers/metamask.js16
-rw-r--r--ui/app/selectors.js61
-rw-r--r--ui/app/send.js533
-rw-r--r--ui/app/unlock.js2
-rw-r--r--ui/app/util.js35
83 files changed, 7033 insertions, 1952 deletions
diff --git a/ui/app/account-and-transaction-details.js b/ui/app/account-and-transaction-details.js
new file mode 100644
index 000000000..60293de77
--- /dev/null
+++ b/ui/app/account-and-transaction-details.js
@@ -0,0 +1,38 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+// Main Views
+const TxView = require('./components/tx-view')
+const WalletView = require('./components/wallet-view')
+
+module.exports = AccountAndTransactionDetails
+
+inherits(AccountAndTransactionDetails, Component)
+function AccountAndTransactionDetails () {
+ Component.call(this)
+}
+
+AccountAndTransactionDetails.prototype.render = function () {
+ return h('div', {
+ style: {
+ display: 'flex',
+ flex: '1 0 auto',
+ },
+ }, [
+ // wallet
+ h(WalletView, {
+ style: {
+ },
+ responsiveDisplayClassname: '.lap-visible',
+ }, [
+ ]),
+
+ // transaction
+ h(TxView, {
+ style: {
+ },
+ }, [
+ ]),
+ ])
+}
+
diff --git a/ui/app/account-detail.js b/ui/app/account-detail.js
index 02089ecd0..d5b9419a7 100644
--- a/ui/app/account-detail.js
+++ b/ui/app/account-detail.js
@@ -5,15 +5,10 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
const valuesFor = require('./util').valuesFor
-const Identicon = require('./components/identicon')
-const EthBalance = require('./components/eth-balance')
const TransactionList = require('./components/transaction-list')
const ExportAccountView = require('./components/account-export')
-const ethUtil = require('ethereumjs-util')
-const EditableLabel = require('./components/editable-label')
const TabBar = require('./components/tab-bar')
const TokenList = require('./components/token-list')
-const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
module.exports = connect(mapStateToProps)(AccountDetailScreen)
@@ -40,179 +35,11 @@ function AccountDetailScreen () {
Component.call(this)
}
-AccountDetailScreen.prototype.render = function () {
- var props = this.props
- var selected = props.address || Object.keys(props.accounts)[0]
- var checksumAddress = selected && ethUtil.toChecksumAddress(selected)
- var identity = props.identities[selected]
- var account = props.accounts[selected]
- const { network, conversionRate, currentCurrency } = props
-
- return (
-
- h('.account-detail-section.full-flex-height', [
-
- // identicon, label, balance, etc
- h('.account-data-subsection', {
- style: {
- margin: '0 20px',
- flex: '1 0 auto',
- },
- }, [
-
- // header - identicon + nav
- h('div', {
- style: {
- paddingTop: '20px',
- display: 'flex',
- justifyContent: 'flex-start',
- alignItems: 'flex-start',
- },
- }, [
-
- // large identicon and addresses
- h('.identicon-wrapper.select-none', [
- h(Identicon, {
- diameter: 62,
- address: selected,
- }),
- ]),
- h('flex-column', {
- style: {
- lineHeight: '10px',
- marginLeft: '15px',
- width: '100%',
- },
- }, [
- h(EditableLabel, {
- textValue: identity ? identity.name : '',
- state: {
- isEditingLabel: false,
- },
- saveText: (text) => {
- props.dispatch(actions.saveAccountLabel(selected, text))
- },
- }, [
-
- // What is shown when not editing + edit text:
- h('label.editing-label', [h('.edit-text', 'edit')]),
- h(
- 'div',
- {
- style: {
- display: 'flex',
- justifyContent: 'flex-start',
- alignItems: 'center',
- },
- },
- [
- h(
- 'div.font-medium.color-forest',
- {
- name: 'edit',
- style: {
- },
- },
- [
- h('h2', {
- style: {
- maxWidth: '180px',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- padding: '5px 0px',
- },
- }, [
- identity && identity.name,
- ]),
- ]
- ),
- h(
- AccountDropdowns,
- {
- style: {
- marginRight: '8px',
- marginLeft: 'auto',
- cursor: 'pointer',
- },
- selected,
- network,
- identities: props.identities,
- enableAccountOptions: true,
- },
- ),
- ]
- ),
- ]),
- h('.flex-row', {
- style: {
- width: '15em',
- justifyContent: 'space-between',
- alignItems: 'baseline',
- },
- }, [
-
- // address
-
- h('div', {
- style: {
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- paddingTop: '3px',
- width: '5em',
- fontSize: '13px',
- fontFamily: 'Montserrat Light',
- textRendering: 'geometricPrecision',
- marginBottom: '15px',
- color: '#AEAEAE',
- },
- }, checksumAddress),
- ]),
-
- // account ballence
-
- ]),
- ]),
- h('.flex-row', {
- style: {
- justifyContent: 'space-between',
- alignItems: 'flex-start',
- },
- }, [
-
- h(EthBalance, {
- value: account && account.balance,
- conversionRate,
- currentCurrency,
- style: {
- lineHeight: '7px',
- marginTop: '10px',
- },
- }),
-
- h('.flex-grow'),
-
- h('button', {
- onClick: () => props.dispatch(actions.buyEthView(selected)),
- style: { marginRight: '10px' },
- }, 'BUY'),
-
- h('button', {
- onClick: () => props.dispatch(actions.showSendPage()),
- style: {
- marginBottom: '20px',
- marginRight: '8px',
- },
- }, 'SEND'),
-
- ]),
- ]),
-
- // subview (tx history, pk export confirm, buy eth warning)
- this.subview(),
-
- ])
- )
-}
+// Note: This component is no longer used. Leaving the file for reference:
+// - structuring routing for add token
+// - state required for TxList
+// Delete file when those features are complete
+AccountDetailScreen.prototype.render = function () {}
AccountDetailScreen.prototype.subview = function () {
var subview
diff --git a/ui/app/actions.js b/ui/app/actions.js
index e793e6a21..678c68a6a 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -1,3 +1,4 @@
+const abi = require('human-standard-token-abi')
const getBuyEthUrl = require('../../app/scripts/lib/buy-eth-url')
var actions = {
@@ -5,6 +6,21 @@ var actions = {
GO_HOME: 'GO_HOME',
goHome: goHome,
+ // modal state
+ MODAL_OPEN: 'UI_MODAL_OPEN',
+ MODAL_CLOSE: 'UI_MODAL_CLOSE',
+ showModal: showModal,
+ hideModal: hideModal,
+ // sidebar state
+ SIDEBAR_OPEN: 'UI_SIDEBAR_OPEN',
+ SIDEBAR_CLOSE: 'UI_SIDEBAR_CLOSE',
+ showSidebar: showSidebar,
+ hideSidebar: hideSidebar,
+ // network dropdown open
+ NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN',
+ NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE',
+ showNetworkDropdown: showNetworkDropdown,
+ hideNetworkDropdown: hideNetworkDropdown,
// menu state
getNetworkStatus: 'getNetworkStatus',
// transition state
@@ -68,6 +84,8 @@ var actions = {
hideWarning: hideWarning,
// accounts screen
SET_SELECTED_ACCOUNT: 'SET_SELECTED_ACCOUNT',
+ SET_SELECTED_TOKEN: 'SET_SELECTED_TOKEN',
+ setSelectedToken,
SHOW_ACCOUNT_DETAIL: 'SHOW_ACCOUNT_DETAIL',
SHOW_ACCOUNTS_PAGE: 'SHOW_ACCOUNTS_PAGE',
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
@@ -78,6 +96,8 @@ var actions = {
// account detail screen
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
showSendPage: showSendPage,
+ SHOW_SEND_TOKEN_PAGE: 'SHOW_SEND_TOKEN_PAGE',
+ showSendTokenPage,
ADD_TO_ADDRESS_BOOK: 'ADD_TO_ADDRESS_BOOK',
addToAddressBook: addToAddressBook,
REQUEST_ACCOUNT_EXPORT: 'REQUEST_ACCOUNT_EXPORT',
@@ -97,7 +117,9 @@ var actions = {
cancelMsg: cancelMsg,
signPersonalMsg,
cancelPersonalMsg,
+ sendTx: sendTx,
signTx: signTx,
+ signTokenTx: signTokenTx,
updateAndApproveTx,
cancelTx: cancelTx,
completedTx: completedTx,
@@ -142,6 +164,8 @@ var actions = {
coinBaseSubview: coinBaseSubview,
SHAPESHIFT_SUBVIEW: 'SHAPESHIFT_SUBVIEW',
shapeShiftSubview: shapeShiftSubview,
+ UPDATE_TOKEN_EXCHANGE_RATE: 'UPDATE_TOKEN_EXCHANGE_RATE',
+ updateTokenExchangeRate,
PAIR_UPDATE: 'PAIR_UPDATE',
pairUpdate: pairUpdate,
coinShiftRquest: coinShiftRquest,
@@ -337,16 +361,16 @@ function showInfoPage () {
function setCurrentCurrency (currencyCode) {
return (dispatch) => {
- dispatch(this.showLoadingIndication())
+ dispatch(actions.showLoadingIndication())
log.debug(`background.setCurrentCurrency`)
background.setCurrentCurrency(currencyCode, (err, data) => {
- dispatch(this.hideLoadingIndication())
+ dispatch(actions.hideLoadingIndication())
if (err) {
log.error(err.stack)
return dispatch(actions.displayWarning(err.message))
}
dispatch({
- type: this.SET_CURRENT_FIAT,
+ type: actions.SET_CURRENT_FIAT,
value: {
currentCurrency: data.currentCurrency,
conversionRate: data.conversionRate,
@@ -400,10 +424,37 @@ function signTx (txData) {
dispatch(actions.showLoadingIndication())
global.ethQuery.sendTransaction(txData, (err, data) => {
dispatch(actions.hideLoadingIndication())
- if (err) dispatch(actions.displayWarning(err.message))
- dispatch(this.goHome())
+ if (err) return dispatch(actions.displayWarning(err.message))
+ dispatch(actions.hideWarning())
})
- dispatch(actions.showConfTxPage())
+ dispatch(actions.showConfTxPage({}))
+ }
+}
+
+function sendTx (txData) {
+ log.info(`actions - sendTx: ${JSON.stringify(txData.txParams)}`)
+ return (dispatch) => {
+ log.debug(`actions calling background.approveTransaction`)
+ background.approveTransaction(txData.id, (err) => {
+ if (err) {
+ dispatch(actions.txError(err))
+ return log.error(err.message)
+ }
+ dispatch(actions.completedTx(txData.id))
+ })
+ }
+}
+
+function signTokenTx (tokenAddress, toAddress, amount, txData) {
+ return dispatch => {
+ dispatch(actions.showLoadingIndication())
+ const token = global.eth.contract(abi).at(tokenAddress)
+ token.transfer(toAddress, amount, txData)
+ .catch(err => {
+ dispatch(actions.hideLoadingIndication())
+ dispatch(actions.displayWarning(err.message))
+ })
+ dispatch(actions.showConfTxPage({}))
}
}
@@ -568,6 +619,13 @@ function setCurrentAccountTab (newTabName) {
return callBackgroundThenUpdateNoSpinner(background.setCurrentAccountTab, newTabName)
}
+function setSelectedToken (tokenAddress) {
+ return {
+ type: actions.SET_SELECTED_TOKEN,
+ value: tokenAddress || null,
+ }
+}
+
function showAccountDetail (address) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
@@ -581,6 +639,7 @@ function showAccountDetail (address) {
type: actions.SHOW_ACCOUNT_DETAIL,
value: address,
})
+ dispatch(actions.setSelectedToken())
})
}
}
@@ -598,10 +657,11 @@ function showAccountsPage () {
}
}
-function showConfTxPage (transForward = true) {
+function showConfTxPage ({transForward = true, id}) {
return {
type: actions.SHOW_CONF_TX_PAGE,
- transForward: transForward,
+ transForward,
+ id,
}
}
@@ -631,10 +691,9 @@ function showConfigPage (transitionForward = true) {
}
}
-function showAddTokenPage (transitionForward = true) {
+function showAddTokenPage () {
return {
type: actions.SHOW_ADD_TOKEN_PAGE,
- value: transitionForward,
}
}
@@ -761,6 +820,46 @@ function useEtherscanProvider () {
}
}
+function showNetworkDropdown () {
+ return {
+ type: actions.NETWORK_DROPDOWN_OPEN,
+ }
+}
+
+function hideNetworkDropdown () {
+ return {
+ type: actions.NETWORK_DROPDOWN_CLOSE,
+ }
+}
+
+
+function showModal (payload) {
+ return {
+ type: actions.MODAL_OPEN,
+ payload,
+ }
+}
+
+function hideModal (payload) {
+ return {
+ type: actions.MODAL_CLOSE,
+ payload,
+ }
+}
+
+function showSidebar () {
+ return {
+ type: actions.SIDEBAR_OPEN,
+ }
+}
+
+function hideSidebar () {
+ return {
+ type: actions.SIDEBAR_CLOSE,
+ }
+}
+
+
function showLoadingIndication (message) {
return {
type: actions.SHOW_LOADING,
@@ -863,6 +962,12 @@ function showSendPage () {
}
}
+function showSendTokenPage () {
+ return {
+ type: actions.SHOW_SEND_TOKEN_PAGE,
+ }
+}
+
function buyEth (opts) {
return (dispatch) => {
const url = getBuyEthUrl(opts)
@@ -989,6 +1094,28 @@ function shapeShiftRequest (query, options, cb) {
}
}
+function updateTokenExchangeRate (token = '') {
+ const pair = `${token.toLowerCase()}_eth`
+
+ return dispatch => {
+ if (!token) {
+ return
+ }
+
+ shapeShiftRequest('marketinfo', { pair }, marketinfo => {
+ if (!marketinfo.error) {
+ dispatch({
+ type: actions.UPDATE_TOKEN_EXCHANGE_RATE,
+ payload: {
+ pair,
+ marketinfo,
+ },
+ })
+ }
+ })
+ }
+}
+
// Call Background Then Update
//
// A function generator for a common pattern wherein:
diff --git a/ui/app/add-token.js b/ui/app/add-token.js
index 18adc7eb5..4374ee586 100644
--- a/ui/app/add-token.js
+++ b/ui/app/add-token.js
@@ -232,7 +232,6 @@ AddTokenScreen.prototype.attemptToAutoFillTokenParams = async function (address)
const [ symbol, decimals ] = results
if (symbol && decimals) {
- console.log('SETTING SYMBOL AND DECIMALS', { symbol, decimals })
this.setState({ symbol: symbol[0], decimals: decimals[0].toString() })
}
}
diff --git a/ui/app/app.js b/ui/app/app.js
index ee800ea90..14e6a26e2 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -6,33 +6,37 @@ const actions = require('./actions')
// init
const InitializeMenuScreen = require('./first-time/init-menu')
const NewKeyChainScreen = require('./new-keychain')
-// unlock
-const UnlockScreen = require('./unlock')
// accounts
-const AccountDetailScreen = require('./account-detail')
+const MainContainer = require('./main-container')
const SendTransactionScreen = require('./send')
+const SendTokenScreen = require('./components/send-token')
const ConfirmTxScreen = require('./conf-tx')
// notice
const NoticeScreen = require('./components/notice')
const generateLostAccountsNotice = require('../lib/lost-accounts-notice')
+
+// slideout menu
+const WalletView = require('./components/wallet-view')
+
// other views
const ConfigScreen = require('./config')
const AddTokenScreen = require('./add-token')
const Import = require('./accounts/import')
const InfoScreen = require('./info')
const Loading = require('./components/loading')
-const SandwichExpando = require('sandwich-expando')
-const Dropdown = require('./components/dropdown').Dropdown
-const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkIndicator = require('./components/network')
const BuyView = require('./components/buy-button-subview')
const QrView = require('./components/qr-code')
const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
-const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
+const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
+const NetworkDropdown = require('./components/dropdowns/network-dropdown')
-module.exports = connect(mapStateToProps)(App)
+// Global Modals
+const Modal = require('./components/modals/index').Modal
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(App)
inherits(App, Component)
function App () { Component.call(this) }
@@ -48,6 +52,8 @@ function mapStateToProps (state) {
return {
// state from plugin
+ networkDropdownOpen: state.appState.networkDropdownOpen,
+ sidebarOpen: state.appState.sidebarOpen,
isLoading: state.appState.isLoading,
loadingMessage: state.appState.loadingMessage,
noActiveNotices: state.metamask.noActiveNotices,
@@ -74,9 +80,18 @@ function mapStateToProps (state) {
}
}
+function mapDispatchToProps (dispatch, ownProps) {
+ return {
+ dispatch,
+ hideSidebar: () => dispatch(actions.hideSidebar()),
+ showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
+ hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
+ }
+}
+
App.prototype.render = function () {
var props = this.props
- const { isLoading, loadingMessage, transForward, network } = props
+ const { isLoading, loadingMessage, network } = props
const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
`Connecting to ${this.getNetworkName()}` : null
@@ -86,316 +101,155 @@ App.prototype.render = function () {
h('.flex-column.full-height', {
style: {
- // Windows was showing a vertical scroll bar:
- overflow: 'hidden',
+ overflowX: 'hidden',
position: 'relative',
alignItems: 'center',
},
}, [
+ // global modal
+ h(Modal, {}, []),
+
// app bar
this.renderAppBar(),
- this.renderNetworkDropdown(),
- this.renderDropdown(),
+
+ // sidebar
+ this.renderSidebar(),
+
+ // network dropdown
+ h(NetworkDropdown, {
+ provider: this.props.provider,
+ frequentRpcList: this.props.frequentRpcList,
+ }, []),
h(Loading, {
isLoading: isLoading || isLoadingNetwork,
loadingMessage: loadMessage,
}),
- // panel content
- h('.app-primary' + (transForward ? '.from-right' : '.from-left'), {
- style: {
- width: '100%',
- },
- }, [
- this.renderPrimary(),
- ]),
+ // content
+ this.renderPrimary(),
])
)
}
+App.prototype.renderGlobalModal = function () {
+ return h(Modal, {
+ ref: 'modalRef',
+ }, [
+ // h(BuyOptions, {}, []),
+ ])
+}
+
+App.prototype.renderSidebar = function () {
+
+ return h('div', {
+ }, [
+ h('style', `
+ .sidebar-enter {
+ transition: transform 300ms ease-in-out;
+ transform: translateX(-100%);
+ }
+ .sidebar-enter.sidebar-enter-active {
+ transition: transform 300ms ease-in-out;
+ transform: translateX(0%);
+ }
+ .sidebar-leave {
+ transition: transform 200ms ease-out;
+ transform: translateX(0%);
+ }
+ .sidebar-leave.sidebar-leave-active {
+ transition: transform 200ms ease-out;
+ transform: translateX(-100%);
+ }
+ `),
+
+ h(ReactCSSTransitionGroup, {
+ transitionName: 'sidebar',
+ transitionEnterTimeout: 300,
+ transitionLeaveTimeout: 200,
+ }, [
+ // A second instance of Walletview is used for non-mobile viewports
+ this.props.sidebarOpen ? h(WalletView, {
+ responsiveDisplayClassname: '.sidebar',
+ style: {},
+ }) : undefined,
+
+ ]),
+
+ // overlay
+ // TODO: add onClick for overlay to close sidebar
+ this.props.sidebarOpen ? h('div.sidebar-overlay', {
+ style: {},
+ onClick: () => {
+ this.props.hideSidebar()
+ },
+ }, []) : undefined,
+ ])
+}
+
App.prototype.renderAppBar = function () {
if (window.METAMASK_UI_TYPE === 'notification') {
return null
}
- const props = this.props
- const state = this.state || {}
- const isNetworkMenuOpen = state.isNetworkMenuOpen || false
-
return (
h('.full-width', {
- height: '38px',
+ style: {},
}, [
h('.app-header.flex-row.flex-space-between', {
- style: {
- alignItems: 'center',
- visibility: props.isUnlocked ? 'visible' : 'none',
- background: props.isUnlocked ? 'white' : 'none',
- height: '38px',
- position: 'relative',
- zIndex: 12,
- },
+ style: {},
}, [
-
- h('div.left-menu-section', {
- style: {
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- },
- }, [
-
- // mini logo
- h('img', {
- height: 24,
- width: 24,
- src: '/images/icon-128.png',
- }),
-
- h(NetworkIndicator, {
- network: this.props.network,
- provider: this.props.provider,
- onClick: (event) => {
- event.preventDefault()
- event.stopPropagation()
- this.setState({ isNetworkMenuOpen: !isNetworkMenuOpen })
- },
- }),
- ]),
-
- // metamask name
- props.isUnlocked && h('h1', {
- style: {
- position: 'relative',
- left: '9px',
- },
- }, 'MetaMask'),
-
- props.isUnlocked && h('div', {
- style: {
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- },
- }, [
-
- props.isUnlocked && h(AccountDropdowns, {
+ h('div.app-header-contents', {}, [
+ h('div.left-menu-wrapper', {
+ style: {},
+ }, [
+ // mini logo
+ h('img', {
+ height: 24,
+ width: 24,
+ src: '/images/icon-128.png',
+ }),
+
+ // metamask name
+ h('h1', {
+ style: {
+ position: 'relative',
+ paddingLeft: '9px',
+ color: '#5B5D67',
+ },
+ }, 'MetaMask'),
+
+ ]),
+
+ h('div.network-component-wrapper', {
style: {},
- enableAccountsSelector: true,
- identities: this.props.identities,
- selected: this.props.currentView.context,
- network: this.props.network,
- keyrings: this.props.keyrings,
- }, []),
-
- // hamburger
- props.isUnlocked && h(SandwichExpando, {
- className: 'sandwich-expando',
- width: 16,
- barHeight: 2,
- padding: 0,
- isOpen: state.isMainMenuOpen,
- color: 'rgb(247,146,30)',
- onClick: () => {
- this.setState({
- isMainMenuOpen: !state.isMainMenuOpen,
- })
- },
- }),
+ }, [
+ // Network Indicator
+ h(NetworkIndicator, {
+ network: this.props.network,
+ provider: this.props.provider,
+ onClick: (event) => {
+ event.preventDefault()
+ event.stopPropagation()
+ if (this.props.networkDropdownOpen === false) {
+ this.props.showNetworkDropdown()
+ } else {
+ this.props.hideNetworkDropdown()
+ }
+ },
+ }),
+
+ ]),
]),
]),
+
])
)
}
-App.prototype.renderNetworkDropdown = function () {
- const props = this.props
- const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
- const rpcList = props.frequentRpcList
- const state = this.state || {}
- const isOpen = state.isNetworkMenuOpen
-
- return h(Dropdown, {
- useCssTransition: true,
- isOpen,
- onClickOutside: (event) => {
- const { classList } = event.target
- const isNotToggleElement = [
- classList.contains('menu-icon'),
- classList.contains('network-name'),
- classList.contains('network-indicator'),
- ].filter(bool => bool).length === 0
- // classes from three constituent nodes of the toggle element
-
- if (isNotToggleElement) {
- this.setState({ isNetworkMenuOpen: false })
- }
- },
- zIndex: 11,
- style: {
- position: 'absolute',
- left: '2px',
- top: '36px',
- },
- innerStyle: {
- padding: '2px 16px 2px 0px',
- },
- }, [
-
- h(
- DropdownMenuItem,
- {
- key: 'main',
- closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => props.dispatch(actions.setProviderType('mainnet')),
- style: {
- fontSize: '18px',
- },
- },
- [
- h('.menu-icon.diamond'),
- 'Main Ethereum Network',
- providerType === 'mainnet' ? h('.check', '✓') : null,
- ]
- ),
-
- h(
- DropdownMenuItem,
- {
- key: 'ropsten',
- closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => props.dispatch(actions.setProviderType('ropsten')),
- style: {
- fontSize: '18px',
- },
- },
- [
- h('.menu-icon.red-dot'),
- 'Ropsten Test Network',
- providerType === 'ropsten' ? h('.check', '✓') : null,
- ]
- ),
-
- h(
- DropdownMenuItem,
- {
- key: 'kovan',
- closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => props.dispatch(actions.setProviderType('kovan')),
- style: {
- fontSize: '18px',
- },
- },
- [
- h('.menu-icon.hollow-diamond'),
- 'Kovan Test Network',
- providerType === 'kovan' ? h('.check', '✓') : null,
- ]
- ),
-
- h(
- DropdownMenuItem,
- {
- key: 'rinkeby',
- closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => props.dispatch(actions.setProviderType('rinkeby')),
- style: {
- fontSize: '18px',
- },
- },
- [
- h('.menu-icon.golden-square'),
- 'Rinkeby Test Network',
- providerType === 'rinkeby' ? h('.check', '✓') : null,
- ]
- ),
-
- h(
- DropdownMenuItem,
- {
- key: 'default',
- closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => props.dispatch(actions.setDefaultRpcTarget()),
- style: {
- fontSize: '18px',
- },
- },
- [
- h('i.fa.fa-question-circle.fa-lg.menu-icon'),
- 'Localhost 8545',
- activeNetwork === 'http://localhost:8545' ? h('.check', '✓') : null,
- ]
- ),
-
- this.renderCustomOption(props.provider),
- this.renderCommonRpc(rpcList, props.provider),
-
- h(
- DropdownMenuItem,
- {
- closeMenu: () => this.setState({ isNetworkMenuOpen: !isOpen }),
- onClick: () => this.props.dispatch(actions.showConfigPage()),
- style: {
- fontSize: '18px',
- },
- },
- [
- h('i.fa.fa-question-circle.fa-lg.menu-icon'),
- 'Custom RPC',
- activeNetwork === 'custom' ? h('.check', '✓') : null,
- ]
- ),
-
- ])
-}
-
-App.prototype.renderDropdown = function () {
- const state = this.state || {}
- const isOpen = state.isMainMenuOpen
-
- return h(Dropdown, {
- useCssTransition: true,
- isOpen: isOpen,
- zIndex: 11,
- onClickOutside: (event) => {
- const classList = event.target.classList
- const parentClassList = event.target.parentElement.classList
-
- const isToggleElement = classList.contains('sandwich-expando') ||
- parentClassList.contains('sandwich-expando')
-
- if (isOpen && !isToggleElement) {
- this.setState({ isMainMenuOpen: false })
- }
- },
- style: {
- position: 'absolute',
- right: '2px',
- top: '38px',
- },
- innerStyle: {},
- }, [
- h(DropdownMenuItem, {
- closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- onClick: () => { this.props.dispatch(actions.showConfigPage()) },
- }, 'Settings'),
-
- h(DropdownMenuItem, {
- closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- onClick: () => { this.props.dispatch(actions.lockMetamask()) },
- }, 'Lock'),
-
- h(DropdownMenuItem, {
- closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
- onClick: () => { this.props.dispatch(actions.showInfoPage()) },
- }, 'Info/Help'),
- ])
-}
-
App.prototype.renderBackButton = function (style, justArrow = false) {
var props = this.props
return (
@@ -459,33 +313,27 @@ App.prototype.renderPrimary = function () {
// show unlock screen
if (!props.isUnlocked) {
- switch (props.currentView.name) {
-
- case 'restoreVault':
- log.debug('rendering restore vault screen')
- return h(HDRestoreVaultScreen, {key: 'HDRestoreVaultScreen'})
-
- case 'config':
- log.debug('rendering config screen from unlock screen.')
- return h(ConfigScreen, {key: 'config'})
-
- default:
- log.debug('rendering locked screen')
- return h(UnlockScreen, {key: 'locked'})
- }
+ return h(MainContainer, {
+ currentViewName: props.currentView.name,
+ isUnlocked: props.isUnlocked,
+ })
}
// show current view
switch (props.currentView.name) {
case 'accountDetail':
- log.debug('rendering account detail screen')
- return h(AccountDetailScreen, {key: 'account-detail'})
+ log.debug('rendering main container')
+ return h(MainContainer, {key: 'account-detail'})
case 'sendTransaction':
log.debug('rendering send tx screen')
return h(SendTransactionScreen, {key: 'send-transaction'})
+ case 'sendToken':
+ log.debug('rendering send token screen')
+ return h(SendTokenScreen, {key: 'sendToken'})
+
case 'newKeychain':
log.debug('rendering new keychain screen')
return h(NewKeyChainScreen, {key: 'new-keychain'})
@@ -548,7 +396,7 @@ App.prototype.renderPrimary = function () {
default:
log.debug('rendering default, account detail screen')
- return h(AccountDetailScreen, {key: 'account-detail'})
+ return h(MainContainer, {key: 'account-detail'})
}
}
@@ -564,40 +412,6 @@ App.prototype.toggleMetamaskActive = function () {
}
}
-App.prototype.renderCustomOption = function (provider) {
- const { rpcTarget, type } = provider
- const props = this.props
-
- if (type !== 'rpc') return null
-
- // Concatenate long URLs
- let label = rpcTarget
- if (rpcTarget.length > 31) {
- label = label.substr(0, 34) + '...'
- }
-
- switch (rpcTarget) {
-
- case 'http://localhost:8545':
- return null
-
- default:
- return h(
- DropdownMenuItem,
- {
- key: rpcTarget,
- onClick: () => props.dispatch(actions.setRpcTarget(rpcTarget)),
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- },
- [
- h('i.fa.fa-question-circle.fa-lg.menu-icon'),
- label,
- h('.check', '✓'),
- ]
- )
- }
-}
-
App.prototype.getNetworkName = function () {
const { provider } = this.props
const providerName = provider.type
@@ -618,28 +432,3 @@ App.prototype.getNetworkName = function () {
return name
}
-
-App.prototype.renderCommonRpc = function (rpcList, provider) {
- const props = this.props
- const rpcTarget = provider.rpcTarget
-
- return rpcList.map((rpc) => {
- if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
- return null
- } else {
- return h(
- DropdownMenuItem,
- {
- key: `common${rpc}`,
- closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
- onClick: () => props.dispatch(actions.setRpcTarget(rpc)),
- },
- [
- h('i.fa.fa-question-circle.fa-lg.menu-icon'),
- rpc,
- rpcTarget === rpc ? h('.check', '✓') : null,
- ]
- )
- }
- })
-}
diff --git a/ui/app/components/balance-component.js b/ui/app/components/balance-component.js
new file mode 100644
index 000000000..d14aa675f
--- /dev/null
+++ b/ui/app/components/balance-component.js
@@ -0,0 +1,120 @@
+const Component = require('react').Component
+const connect = require('react-redux').connect
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const TokenBalance = require('./token-balance')
+const Identicon = require('./identicon')
+
+const { formatBalance, generateBalanceObject } = require('../util')
+
+module.exports = connect(mapStateToProps)(BalanceComponent)
+
+function mapStateToProps (state) {
+ const accounts = state.metamask.accounts
+ const network = state.metamask.network
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
+ const account = accounts[selectedAddress]
+
+ return {
+ account,
+ network,
+ conversionRate: state.metamask.conversionRate,
+ currentCurrency: state.metamask.currentCurrency,
+ }
+}
+
+inherits(BalanceComponent, Component)
+function BalanceComponent () {
+ Component.call(this)
+}
+
+BalanceComponent.prototype.render = function () {
+ const props = this.props
+ const { token, network } = props
+
+ return h('div.balance-container', {}, [
+
+ // TODO: balance icon needs to be passed in
+ // h('img.balance-icon', {
+ // src: '../images/eth_logo.svg',
+ // style: {},
+ // }),
+ h(Identicon, {
+ diameter: 45,
+ address: token && token.address,
+ network,
+ }),
+
+ token ? this.renderTokenBalance() : this.renderBalance(),
+ ])
+}
+
+BalanceComponent.prototype.renderTokenBalance = function () {
+ const { token } = this.props
+
+ return h('div.flex-column.balance-display', [
+ h('div.token-amount', [ h(TokenBalance, { token }) ]),
+ ])
+}
+
+BalanceComponent.prototype.renderBalance = function () {
+ const props = this.props
+ const { shorten, account } = props
+ const balanceValue = account && account.balance
+ const needsParse = 'needsParse' in props ? props.needsParse : true
+ const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
+ const showFiat = 'showFiat' in props ? props.showFiat : true
+
+ if (formattedBalance === 'None' || formattedBalance === '...') {
+ return h('div.flex-column.balance-display', {}, [
+ h('div.token-amount', {
+ style: {},
+ }, formattedBalance),
+ ])
+ }
+
+ return h('div.flex-column.balance-display', {}, [
+ h('div.token-amount', {
+ style: {},
+ }, this.getTokenBalance(formattedBalance, shorten)),
+
+ showFiat ? this.renderFiatValue(formattedBalance) : null,
+ ])
+}
+
+BalanceComponent.prototype.renderFiatValue = function (formattedBalance) {
+
+ const { conversionRate, currentCurrency } = this.props
+
+ const fiatDisplayNumber = this.getFiatDisplayNumber(formattedBalance, conversionRate)
+
+ const fiatPrefix = currentCurrency === 'USD' ? '$' : ''
+
+ return this.renderFiatAmount(fiatDisplayNumber, currentCurrency, fiatPrefix)
+}
+
+BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatSuffix, fiatPrefix) {
+ if (fiatDisplayNumber === 'N/A') return null
+
+ return h('div.fiat-amount', {
+ style: {},
+ }, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`)
+}
+
+BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
+ const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
+
+ const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance
+ const label = balanceObj.label
+
+ return `${balanceValue} ${label}`
+}
+
+BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
+ if (formattedBalance === 'None') return formattedBalance
+ if (conversionRate === 0) return 'N/A'
+
+ const splitBalance = formattedBalance.split(' ')
+
+ return (Number(splitBalance[0]) * conversionRate).toFixed(2)
+}
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index 15281171c..6cf6e9eb9 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -245,7 +245,7 @@ BuyButtonSubview.prototype.navigateTo = function (url) {
BuyButtonSubview.prototype.backButtonContext = function () {
if (this.props.context === 'confTx') {
- this.props.dispatch(actions.showConfTxPage(false))
+ this.props.dispatch(actions.showConfTxPage({transForward: false}))
} else {
this.props.dispatch(actions.goHome())
}
diff --git a/ui/app/components/dropdowns/account-options-dropdown.js b/ui/app/components/dropdowns/account-options-dropdown.js
new file mode 100644
index 000000000..50e793d87
--- /dev/null
+++ b/ui/app/components/dropdowns/account-options-dropdown.js
@@ -0,0 +1,29 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const AccountDropdowns = require('./components/account-dropdowns')
+
+inherits(AccountOptionsDropdown, Component)
+function AccountOptionsDropdown () {
+ Component.call(this)
+}
+
+module.exports = AccountOptionsDropdown
+
+// TODO: specify default props and proptypes
+// TODO: hook up to state, connect to redux to clean up API
+// TODO: selectedAddress is not defined... should we use selected?
+AccountOptionsDropdown.prototype.render = function () {
+ const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props
+
+ return h(AccountDropdowns, {
+ enableAccountOptions: true,
+ enableAccountsSelector: false,
+ selected: selectedAddress,
+ network,
+ identities,
+ style: style || {},
+ dropdownWrapperStyle: dropdownWrapperStyle || {},
+ menuItemStyles: menuItemStyles || {},
+ }, [])
+}
diff --git a/ui/app/components/dropdowns/account-selection-dropdown.js b/ui/app/components/dropdowns/account-selection-dropdown.js
new file mode 100644
index 000000000..7a8502d18
--- /dev/null
+++ b/ui/app/components/dropdowns/account-selection-dropdown.js
@@ -0,0 +1,29 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const AccountDropdowns = require('./components/account-dropdowns')
+
+inherits(AccountSelectionDropdown, Component)
+function AccountSelectionDropdown () {
+ Component.call(this)
+}
+
+module.exports = AccountSelectionDropdown
+
+// TODO: specify default props and proptypes
+// TODO: hook up to state, connect to redux to clean up API
+// TODO: selectedAddress is not defined... should we use selected?
+AccountSelectionDropdown.prototype.render = function () {
+ const { selected, network, identities, style, dropdownWrapperStyle, menuItemStyles } = this.props
+
+ return h(AccountDropdowns, {
+ enableAccountOptions: false,
+ enableAccountsSelector: true,
+ selected: selectedAddress,
+ network,
+ identities,
+ style: style || {},
+ dropdownWrapperStyle: dropdownWrapperStyle || {},
+ menuItemStyles: menuItemStyles || {},
+ }, [])
+}
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js
index b087a40d4..e2d3d6d64 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/dropdowns/components/account-dropdowns.js
@@ -1,14 +1,15 @@
const Component = require('react').Component
const PropTypes = require('react').PropTypes
const h = require('react-hyperscript')
-const actions = require('../actions')
-const genAccountLink = require('../../lib/account-link.js')
+const actions = require('../../../actions')
+const genAccountLink = require('../../../../lib/account-link.js')
const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem
-const Identicon = require('./identicon')
+const Identicon = require('../../identicon')
const ethUtil = require('ethereumjs-util')
const copyToClipboard = require('copy-to-clipboard')
+const { formatBalance } = require('../../../util')
class AccountDropdowns extends Component {
constructor (props) {
@@ -17,17 +18,21 @@ class AccountDropdowns extends Component {
accountSelectorActive: false,
optionsMenuActive: false,
}
- this.accountSelectorToggleClassName = 'accounts-selector'
+ // Used for orangeaccount selector icon
+ // this.accountSelectorToggleClassName = 'accounts-selector'
+ this.accountSelectorToggleClassName = 'fa-angle-down'
this.optionsMenuToggleClassName = 'fa-ellipsis-h'
}
renderAccounts () {
- const { identities, selected, keyrings } = this.props
+ const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
const isSelected = identity.address === selected
+ const balanceValue = accounts[key].balance
+ const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
@@ -42,34 +47,108 @@ class AccountDropdowns extends Component {
onClick: () => {
this.props.actions.showAccountDetail(identity.address)
},
- style: {
- marginTop: index === 0 ? '5px' : '',
- fontSize: '24px',
- },
+ style: Object.assign(
+ {
+ marginTop: index === 0 ? '5px' : '',
+ fontSize: '24px',
+ },
+ menuItemStyles,
+ ),
},
[
- h(
- Identicon,
- {
- address: identity.address,
- diameter: 32,
+ h('div.flex-row.flex-center', {}, [
+
+ h('span', {
+ style: {
+ flex: '1 1 0',
+ minWidth: '20px',
+ minHeight: '30px',
+ },
+ }, [
+ h('span', {
+ style: {
+ flex: '1 1 auto',
+ fontSize: '14px',
+ },
+ }, isSelected ? h('i.fa.fa-check') : null),
+ ]),
+
+ h(
+ Identicon,
+ {
+ address: identity.address,
+ diameter: 24,
+ style: {
+ flex: '1 1 auto',
+ marginLeft: '10px',
+ },
+ },
+ ),
+
+ h('span.flex-column', {
style: {
+ flex: '10 10 auto',
+ width: '175px',
+ alignItems: 'flex-start',
+ justifyContent: 'center',
marginLeft: '10px',
},
- },
- ),
- this.indicateIfLoose(keyring),
- h('span', {
- style: {
- marginLeft: '20px',
- fontSize: '24px',
- maxWidth: '145px',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- },
- }, identity.name || ''),
- h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
+ }, [
+ this.indicateIfLoose(keyring),
+ h('span.account-dropdown-name', {
+ style: {
+ fontSize: '18px',
+ maxWidth: '145px',
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ },
+ }, identity.name || ''),
+ h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
+
+ h('span.account-dropdown-balance', {
+ style: {
+ fontSize: '14px',
+ fontFamily: 'Avenir',
+ fontWeight: 500,
+ },
+ }, formattedBalance),
+ ]),
+
+ h('span', {
+ style: {
+ flex: '3 3 auto',
+ },
+ }, [
+ h('span.account-dropdown-edit-button', {
+ style: {
+ fontSize: '16px',
+ },
+ onClick: () => {
+ actions.showEditAccountModal(identity)
+ },
+ }, [
+ 'Edit',
+ ]),
+ ]),
+
+ ]),
+// =======
+// },
+// ),
+// this.indicateIfLoose(keyring),
+// h('span', {
+// style: {
+// marginLeft: '20px',
+// fontSize: '24px',
+// maxWidth: '145px',
+// whiteSpace: 'nowrap',
+// overflow: 'hidden',
+// textOverflow: 'ellipsis',
+// },
+// }, identity.name || ''),
+// h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, isSelected ? h('.check', '✓') : null),
+// >>>>>>> master:ui/app/components/account-dropdowns.js
]
)
})
@@ -84,24 +163,22 @@ class AccountDropdowns extends Component {
}
renderAccountSelector () {
- const { actions } = this.props
- const { accountSelectorActive } = this.state
+ const { actions, useCssTransition, innerStyle } = this.props
+ const { accountSelectorActive, menuItemStyles } = this.state
return h(
Dropdown,
{
- useCssTransition: true, // Hardcoded because account selector is temporarily in app-header
+ useCssTransition,
style: {
- marginLeft: '-238px',
- marginTop: '38px',
+ marginLeft: '-185px',
+ marginTop: '50px',
minWidth: '180px',
overflowY: 'auto',
maxHeight: '300px',
width: '300px',
},
- innerStyle: {
- padding: '8px 25px',
- },
+ innerStyle,
isOpen: accountSelectorActive,
onClickOutside: (event) => {
const { classList } = event.target
@@ -117,7 +194,11 @@ class AccountDropdowns extends Component {
DropdownMenuItem,
{
closeMenu: () => {},
- onClick: () => actions.addNewAccount(),
+ style: Object.assign(
+ {},
+ menuItemStyles,
+ ),
+ onClick: () => actions.showNewAccountModal(),
},
[
h(
@@ -129,7 +210,14 @@ class AccountDropdowns extends Component {
diameter: 32,
},
),
- h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, 'Create Account'),
+ h('span', {
+ style: {
+ marginLeft: '20px',
+ fontFamily: 'DIN OT',
+ fontSize: '16px',
+ lineHeight: '23px',
+ },
+ }, 'Create Account'),
],
),
h(
@@ -137,6 +225,10 @@ class AccountDropdowns extends Component {
{
closeMenu: () => {},
onClick: () => actions.showImportPage(),
+ style: Object.assign(
+ {},
+ menuItemStyles,
+ ),
},
[
h(
@@ -151,8 +243,10 @@ class AccountDropdowns extends Component {
h('span', {
style: {
marginLeft: '20px',
- fontSize: '24px',
marginBottom: '5px',
+ fontFamily: 'DIN OT',
+ fontSize: '16px',
+ lineHeight: '23px',
},
}, 'Import Account'),
]
@@ -161,19 +255,28 @@ class AccountDropdowns extends Component {
)
}
-
-
renderAccountOptions () {
- const { actions } = this.props
- const { optionsMenuActive } = this.state
+ const { actions, dropdownWrapperStyle, useCssTransition } = this.props
+ const { optionsMenuActive, menuItemStyles } = this.state
+ const dropdownMenuItemStyle = {
+ fontFamily: 'DIN OT',
+ fontSize: 16,
+ lineHeight: '24px',
+ padding: '8px',
+ }
return h(
Dropdown,
{
- style: {
- marginLeft: '-215px',
- minWidth: '180px',
- },
+ useCssTransition,
+ style: Object.assign(
+ {
+ marginLeft: '-10px',
+ position: 'absolute',
+ width: '29vh', // affects both mobile and laptop views
+ },
+ dropdownWrapperStyle,
+ ),
isOpen: optionsMenuActive,
onClickOutside: () => {
const { classList } = event.target
@@ -189,24 +292,30 @@ class AccountDropdowns extends Component {
{
closeMenu: () => {},
onClick: () => {
- const { selected, network } = this.props
- const url = genAccountLink(selected, network)
- global.platform.openWindow({ url })
+ this.props.actions.showAccountDetailModal()
},
+ style: Object.assign(
+ dropdownMenuItemStyle,
+ menuItemStyles,
+ ),
},
- 'View account on Etherscan',
+ 'Account Details',
),
h(
DropdownMenuItem,
{
closeMenu: () => {},
onClick: () => {
- const { selected, identities } = this.props
- var identity = identities[selected]
- actions.showQrView(selected, identity ? identity.name : '')
+ const { selected, network } = this.props
+ const url = genAccountLink(selected, network)
+ global.platform.openWindow({ url })
},
+ style: Object.assign(
+ dropdownMenuItemStyle,
+ menuItemStyles,
+ ),
},
- 'Show QR Code',
+ 'View account on Etherscan',
),
h(
DropdownMenuItem,
@@ -217,6 +326,10 @@ class AccountDropdowns extends Component {
const checkSumAddress = selected && ethUtil.toChecksumAddress(selected)
copyToClipboard(checkSumAddress)
},
+ style: Object.assign(
+ dropdownMenuItemStyle,
+ menuItemStyles,
+ ),
},
'Copy Address to clipboard',
),
@@ -227,9 +340,28 @@ class AccountDropdowns extends Component {
onClick: () => {
actions.requestAccountExport()
},
+ style: Object.assign(
+ dropdownMenuItemStyle,
+ menuItemStyles,
+ ),
},
'Export Private Key',
),
+ h(
+ DropdownMenuItem,
+ {
+ closeMenu: () => {},
+ onClick: () => {
+ actions.showAddTokenPage()
+ },
+ style: Object.assign(
+ dropdownMenuItemStyle,
+ menuItemStyles,
+ ),
+ },
+ 'Add Token',
+ ),
+
]
)
}
@@ -245,16 +377,10 @@ class AccountDropdowns extends Component {
},
[
enableAccountsSelector && h(
- // 'i.fa.fa-angle-down',
- 'div.cursor-pointer.color-orange.accounts-selector',
+ 'i.fa.fa-angle-down',
{
style: {
- // fontSize: '1.8em',
- background: 'url(images/switch_acc.svg) white center center no-repeat',
- height: '25px',
- width: '25px',
- transform: 'scale(0.75)',
- marginRight: '3px',
+ cursor: 'pointer',
},
onClick: (event) => {
event.stopPropagation()
@@ -270,8 +396,8 @@ class AccountDropdowns extends Component {
'i.fa.fa-ellipsis-h',
{
style: {
- marginRight: '0.5em',
- fontSize: '1.8em',
+ fontSize: '135%',
+ cursor: 'pointer',
},
onClick: (event) => {
event.stopPropagation()
@@ -305,6 +431,21 @@ const mapDispatchToProps = (dispatch) => {
showConfigPage: () => dispatch(actions.showConfigPage()),
requestAccountExport: () => dispatch(actions.requestExportAccount()),
showAccountDetail: (address) => dispatch(actions.showAccountDetail(address)),
+ showAccountDetailModal: () => {
+ dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
+ },
+ showEditAccountModal: (identity) => {
+ dispatch(actions.showModal({
+ name: 'EDIT_ACCOUNT_NAME',
+ identity,
+ }))
+ },
+ showNewAccountModal: () => {
+ dispatch(actions.showModal({ name: 'NEW_ACCOUNT' }))
+ },
+ showAddTokenPage: () => {
+ dispatch(actions.showAddTokenPage())
+ },
addNewAccount: () => dispatch(actions.addNewAccount()),
showImportPage: () => dispatch(actions.showImportPage()),
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
@@ -312,6 +453,11 @@ const mapDispatchToProps = (dispatch) => {
}
}
-module.exports = {
- AccountDropdowns: connect(null, mapDispatchToProps)(AccountDropdowns),
+function mapStateToProps (state) {
+ return {
+ keyrings: state.metamask.keyrings,
+ }
}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDropdowns)
+
diff --git a/ui/app/components/dropdown.js b/ui/app/components/dropdowns/components/dropdown.js
index 73710acc2..991c89cb8 100644
--- a/ui/app/components/dropdown.js
+++ b/ui/app/components/dropdowns/components/dropdown.js
@@ -1,14 +1,22 @@
const Component = require('react').Component
const PropTypes = require('react').PropTypes
const h = require('react-hyperscript')
-const MenuDroppo = require('./menu-droppo')
+const MenuDroppo = require('../../menu-droppo')
const extend = require('xtend')
const noop = () => {}
class Dropdown extends Component {
render () {
- const { isOpen, onClickOutside, style, innerStyle, children, useCssTransition } = this.props
+ const {
+ containerClassName,
+ isOpen,
+ onClickOutside,
+ style,
+ innerStyle,
+ children,
+ useCssTransition,
+ } = this.props
const innerStyleDefaults = extend({
borderRadius: '4px',
@@ -20,9 +28,10 @@ class Dropdown extends Component {
return h(
MenuDroppo,
{
+ containerClassName,
useCssTransition,
isOpen,
- zIndex: 11,
+ zIndex: 30,
onClickOutside,
style,
innerStyle: innerStyleDefaults,
@@ -31,8 +40,12 @@ class Dropdown extends Component {
h(
'style',
`
- li.dropdown-menu-item:hover { color:rgb(225, 225, 225); }
- li.dropdown-menu-item { color: rgb(185, 185, 185); position: relative }
+ li.dropdown-menu-item:hover {
+ color:rgb(225, 225, 225);
+ background-color: rgba(255, 255, 255, 0.05);
+ border-radius: 4px;
+ }
+ li.dropdown-menu-item { color: rgb(185, 185, 185); }
`
),
...children,
@@ -67,7 +80,7 @@ class DropdownMenuItem extends Component {
},
style: Object.assign({
listStyle: 'none',
- padding: '8px 0px 8px 0px',
+ padding: '8px 0px',
fontSize: '18px',
fontStyle: 'normal',
fontFamily: 'Montserrat Regular',
@@ -75,6 +88,7 @@ class DropdownMenuItem extends Component {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
+ color: 'white',
}, style),
},
children
diff --git a/ui/app/components/dropdowns/components/network-dropdown-icon.js b/ui/app/components/dropdowns/components/network-dropdown-icon.js
new file mode 100644
index 000000000..7e94e0af5
--- /dev/null
+++ b/ui/app/components/dropdowns/components/network-dropdown-icon.js
@@ -0,0 +1,28 @@
+const inherits = require('util').inherits
+const Component = require('react').Component
+const h = require('react-hyperscript')
+
+
+inherits(NetworkDropdownIcon, Component)
+module.exports = NetworkDropdownIcon
+
+function NetworkDropdownIcon () {
+ Component.call(this)
+}
+
+NetworkDropdownIcon.prototype.render = function () {
+ const {
+ backgroundColor,
+ isSelected,
+ innerBorder = 'none',
+ } = this.props
+
+ return h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {},
+ h('div', {
+ style: {
+ background: backgroundColor,
+ border: innerBorder,
+ },
+ })
+ )
+}
diff --git a/ui/app/components/dropdowns/index.js b/ui/app/components/dropdowns/index.js
new file mode 100644
index 000000000..fa66f5000
--- /dev/null
+++ b/ui/app/components/dropdowns/index.js
@@ -0,0 +1,17 @@
+// Reusable Dropdown Components
+// TODO: Refactor into separate components
+const Dropdown = require('./components/dropdown').Dropdown
+const AccountDropdowns = require('./components/account-dropdowns')
+
+// App-Specific Instances
+const AccountSelectionDropdown = require('./account-selection-dropdown')
+const AccountOptionsDropdown = require('./account-options-dropdown')
+const NetworkDropdown = require('./network-dropdown').default
+
+module.exports = {
+ AccountSelectionDropdown,
+ AccountOptionsDropdown,
+ NetworkDropdown,
+ Dropdown,
+ AccountDropdowns,
+}
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js
new file mode 100644
index 000000000..4c578fbeb
--- /dev/null
+++ b/ui/app/components/dropdowns/network-dropdown.js
@@ -0,0 +1,315 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const Dropdown = require('./components/dropdown').Dropdown
+const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
+const NetworkDropdownIcon = require('./components/network-dropdown-icon')
+
+function mapStateToProps (state) {
+ return {
+ provider: state.metamask.provider,
+ frequentRpcList: state.metamask.frequentRpcList || [],
+ networkDropdownOpen: state.appState.networkDropdownOpen,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ setProviderType: (type) => {
+ dispatch(actions.setProviderType(type))
+ },
+ setDefaultRpcTarget: type => {
+ dispatch(actions.setDefaultRpcTarget(type))
+ },
+ setRpcTarget: (target) => {
+ dispatch(actions.setRpcTarget(target))
+ },
+ showConfigPage: () => {
+ dispatch(actions.showConfigPage())
+ },
+ showNetworkDropdown: () => { dispatch(actions.showNetworkDropdown()) },
+ hideNetworkDropdown: () => { dispatch(actions.hideNetworkDropdown()) },
+ }
+}
+
+
+inherits(NetworkDropdown, Component)
+function NetworkDropdown () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(NetworkDropdown)
+
+// TODO: specify default props and proptypes
+NetworkDropdown.prototype.render = function () {
+ const props = this.props
+ const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
+ const rpcList = props.frequentRpcList
+ const isOpen = this.props.networkDropdownOpen
+ const dropdownMenuItemStyle = {
+ fontFamily: 'DIN OT',
+ fontSize: '16px',
+ lineHeight: '20px',
+ }
+
+ return h(Dropdown, {
+ useCssTransition: true,
+ isOpen,
+ onClickOutside: (event) => {
+ const { classList } = event.target
+ const isNotToggleElement = [
+ classList.contains('menu-icon'),
+ classList.contains('network-name'),
+ classList.contains('network-indicator'),
+ ].filter(bool => bool).length === 0
+ // classes from three constituent nodes of the toggle element
+
+ if (isNotToggleElement) {
+ this.props.hideNetworkDropdown()
+ }
+ },
+ containerClassName: 'network-droppo',
+ zIndex: 11,
+ style: {
+ position: 'absolute',
+ top: '38px',
+ minWidth: '309px',
+ },
+ innerStyle: {
+ padding: '10px 8px',
+ },
+ }, [
+
+ h('div.network-dropdown-header', {}, [
+ h('div.network-dropdown-title', {}, 'Networks'),
+
+ h('div.network-dropdown-divider'),
+
+ h('div.network-dropdown-content',
+ {},
+ 'The default network for Ether transactions is Main Net.'
+ ),
+ ]),
+
+ h(
+ DropdownMenuItem,
+ {
+ key: 'main',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setProviderType('mainnet'),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ providerType === 'mainnet' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#038789', // $blue-lagoon
+ isSelected: providerType === 'mainnet',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b',
+ },
+ }, 'Main Ethereum Network'),
+ ]
+ ),
+
+ h(
+ DropdownMenuItem,
+ {
+ key: 'ropsten',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setProviderType('ropsten'),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ providerType === 'ropsten' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#e91550', // $crimson
+ isSelected: providerType === 'ropsten',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b',
+ },
+ }, 'Ropsten Test Network'),
+ ]
+ ),
+
+ h(
+ DropdownMenuItem,
+ {
+ key: 'kovan',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setProviderType('kovan'),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ providerType === 'kovan' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#690496', // $purple
+ isSelected: providerType === 'kovan',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b',
+ },
+ }, 'Kovan Test Network'),
+ ]
+ ),
+
+ h(
+ DropdownMenuItem,
+ {
+ key: 'rinkeby',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setProviderType('rinkeby'),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ providerType === 'rinkeby' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#ebb33f', // $tulip-tree
+ isSelected: providerType === 'rinkeby',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b',
+ },
+ }, 'Rinkeby Test Network'),
+ ]
+ ),
+
+ h(
+ DropdownMenuItem,
+ {
+ key: 'default',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setDefaultRpcTarget(),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ activeNetwork === 'http://localhost:8545' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ isSelected: activeNetwork === 'http://localhost:8545',
+ innerBorder: '1px solid #9b9b9b',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: activeNetwork === 'http://localhost:8545' ? '#ffffff' : '#9b9b9b',
+ },
+ }, 'Localhost 8545'),
+ ]
+ ),
+
+ this.renderCustomOption(props.provider),
+ this.renderCommonRpc(rpcList, props.provider),
+
+ h(
+ DropdownMenuItem,
+ {
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => this.props.showConfigPage(),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ activeNetwork === 'custom' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ isSelected: activeNetwork === 'custom',
+ innerBorder: '1px solid #9b9b9b',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b',
+ },
+ }, 'Custom RPC'),
+ ]
+ ),
+
+ ])
+}
+
+
+NetworkDropdown.prototype.getNetworkName = function () {
+ const { provider } = this.props
+ const providerName = provider.type
+
+ let name
+
+ if (providerName === 'mainnet') {
+ name = 'Main Ethereum Network'
+ } else if (providerName === 'ropsten') {
+ name = 'Ropsten Test Network'
+ } else if (providerName === 'kovan') {
+ name = 'Kovan Test Network'
+ } else if (providerName === 'rinkeby') {
+ name = 'Rinkeby Test Network'
+ } else {
+ name = 'Unknown Private Network'
+ }
+
+ return name
+}
+
+NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
+ const props = this.props
+ const rpcTarget = provider.rpcTarget
+
+ return rpcList.map((rpc) => {
+ if ((rpc === 'http://localhost:8545') || (rpc === rpcTarget)) {
+ return null
+ } else {
+ return h(
+ DropdownMenuItem,
+ {
+ key: `common${rpc}`,
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => props.setRpcTarget(rpc),
+ },
+ [
+ h('i.fa.fa-question-circle.fa-lg.menu-icon'),
+ rpc,
+ rpcTarget === rpc ? h('.check', '✓') : null,
+ ]
+ )
+ }
+ })
+}
+
+NetworkDropdown.prototype.renderCustomOption = function (provider) {
+ const { rpcTarget, type } = provider
+ const props = this.props
+
+ if (type !== 'rpc') return null
+
+ // Concatenate long URLs
+ let label = rpcTarget
+ if (rpcTarget.length > 31) {
+ label = label.substr(0, 34) + '...'
+ }
+
+ switch (rpcTarget) {
+
+ case 'http://localhost:8545':
+ return null
+
+ default:
+ return h(
+ DropdownMenuItem,
+ {
+ key: rpcTarget,
+ onClick: () => props.setRpcTarget(rpcTarget),
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ },
+ [
+ h('i.fa.fa-question-circle.fa-lg.menu-icon'),
+ label,
+ h('.check', '✓'),
+ ]
+ )
+ }
+}
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index 3a33ebf74..fb8c8e579 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -44,7 +44,7 @@ EnsInput.prototype.render = function () {
return h('div', {
style: { width: '100%' },
}, [
- h('input.large-input', opts),
+ h('input.large-input.send-screen-input', opts),
// The address book functionality.
h('datalist#addresses',
[
@@ -125,7 +125,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
EnsInput.prototype.ensIcon = function (recipient) {
const { hoverText } = this.state || {}
- return h('span', {
+ return h('span.#ensIcon', {
title: hoverText,
style: {
position: 'absolute',
diff --git a/ui/app/components/eth-balance.js b/ui/app/components/eth-balance.js
index 4f538fd31..1be8c9731 100644
--- a/ui/app/components/eth-balance.js
+++ b/ui/app/components/eth-balance.js
@@ -1,8 +1,10 @@
-const Component = require('react').Component
+const { Component } = require('react')
const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const formatBalance = require('../util').formatBalance
-const generateBalanceObject = require('../util').generateBalanceObject
+const { inherits } = require('util')
+const {
+ formatBalance,
+ generateBalanceObject,
+} = require('../util')
const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js')
@@ -14,11 +16,10 @@ function EthBalanceComponent () {
}
EthBalanceComponent.prototype.render = function () {
- var props = this.props
- let { value } = props
- const { style, width } = props
- var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
- value = value ? formatBalance(value, 6, needsParse) : '...'
+ const props = this.props
+ const { value, style, width, needsParse = true } = props
+
+ const formattedValue = value ? formatBalance(value, 6, needsParse) : '...'
return (
@@ -30,60 +31,66 @@ EthBalanceComponent.prototype.render = function () {
display: 'inline',
width,
},
- }, this.renderBalance(value)),
+ }, this.renderBalance(formattedValue)),
])
)
}
EthBalanceComponent.prototype.renderBalance = function (value) {
- var props = this.props
- const { conversionRate, shorten, incoming, currentCurrency } = props
if (value === 'None') return value
if (value === '...') return value
- var balanceObj = generateBalanceObject(value, shorten ? 1 : 3)
- var balance
- var splitBalance = value.split(' ')
- var ethNumber = splitBalance[0]
- var ethSuffix = splitBalance[1]
- const showFiat = 'showFiat' in props ? props.showFiat : true
- if (shorten) {
- balance = balanceObj.shortBalance
- } else {
- balance = balanceObj.balance
- }
+ const {
+ conversionRate,
+ shorten,
+ incoming,
+ currentCurrency,
+ hideTooltip,
+ styleOveride,
+ showFiat = true,
+ } = this.props
+ const { fontSize, color, fontFamily, lineHeight } = styleOveride
- var label = balanceObj.label
+ const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3)
+ const balanceToRender = shorten ? shortBalance : balance
+
+ const [ethNumber, ethSuffix] = value.split(' ')
+ const containerProps = hideTooltip ? {} : {
+ position: 'bottom',
+ title: `${ethNumber} ${ethSuffix}`,
+ }
return (
- h(Tooltip, {
- position: 'bottom',
- title: `${ethNumber} ${ethSuffix}`,
- }, h('div.flex-column', [
- h('.flex-row', {
- style: {
- alignItems: 'flex-end',
- lineHeight: '13px',
- fontFamily: 'Montserrat Light',
- textRendering: 'geometricPrecision',
- },
- }, [
- h('div', {
- style: {
- width: '100%',
- textAlign: 'right',
- },
- }, incoming ? `+${balance}` : balance),
- h('div', {
+ h(hideTooltip ? 'div' : Tooltip,
+ containerProps,
+ h('div.flex-column', [
+ h('.flex-row', {
style: {
- color: ' #AEAEAE',
- fontSize: '12px',
- marginLeft: '5px',
+ alignItems: 'flex-end',
+ lineHeight: lineHeight || '13px',
+ fontFamily: fontFamily || 'Montserrat Light',
+ textRendering: 'geometricPrecision',
},
- }, label),
- ]),
+ }, [
+ h('div', {
+ style: {
+ width: '100%',
+ textAlign: 'right',
+ fontSize: fontSize || 'inherit',
+ color: color || 'inherit',
+ },
+ }, incoming ? `+${balanceToRender}` : balanceToRender),
+ h('div', {
+ style: {
+ color: color || '#AEAEAE',
+ fontSize: fontSize || '12px',
+ marginLeft: '5px',
+ },
+ }, label),
+ ]),
- showFiat ? h(FiatValue, { value: props.value, conversionRate, currentCurrency }) : null,
- ]))
+ showFiat ? h(FiatValue, { value: this.props.value, conversionRate, currentCurrency }) : null,
+ ])
+ )
)
}
diff --git a/ui/app/components/fiat-value.js b/ui/app/components/fiat-value.js
index 8a64a1cfc..665789353 100644
--- a/ui/app/components/fiat-value.js
+++ b/ui/app/components/fiat-value.js
@@ -12,7 +12,7 @@ function FiatValue () {
FiatValue.prototype.render = function () {
const props = this.props
- const { conversionRate, currentCurrency } = props
+ const { conversionRate, currentCurrency, style } = props
const value = formatBalance(props.value, 6)
@@ -28,16 +28,18 @@ FiatValue.prototype.render = function () {
fiatTooltipNumber = 'Unknown'
}
- return fiatDisplay(fiatDisplayNumber, currentCurrency)
+ return fiatDisplay(fiatDisplayNumber, currentCurrency, style)
}
-function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
+function fiatDisplay (fiatDisplayNumber, fiatSuffix, styleOveride = {}) {
+ const { fontSize, color, fontFamily, lineHeight } = styleOveride
+
if (fiatDisplayNumber !== 'N/A') {
return h('.flex-row', {
style: {
alignItems: 'flex-end',
- lineHeight: '13px',
- fontFamily: 'Montserrat Light',
+ lineHeight: lineHeight || '13px',
+ fontFamily: fontFamily || 'Montserrat Light',
textRendering: 'geometricPrecision',
},
}, [
@@ -45,15 +47,15 @@ function fiatDisplay (fiatDisplayNumber, fiatSuffix) {
style: {
width: '100%',
textAlign: 'right',
- fontSize: '12px',
- color: '#333333',
+ fontSize: fontSize || '12px',
+ color: color || '#333333',
},
}, fiatDisplayNumber),
h('div', {
style: {
- color: '#AEAEAE',
+ color: color || '#AEAEAE',
marginLeft: '5px',
- fontSize: '12px',
+ fontSize: fontSize || '12px',
},
}, fiatSuffix),
])
diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js
index c754bc6ba..259fa4d73 100644
--- a/ui/app/components/identicon.js
+++ b/ui/app/components/identicon.js
@@ -18,21 +18,35 @@ function IdenticonComponent () {
IdenticonComponent.prototype.render = function () {
var props = this.props
+ const { className = '', address } = props
var diameter = props.diameter || this.defaultDiameter
- return (
- h('div', {
- key: 'identicon-' + this.props.address,
- style: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- height: diameter,
- width: diameter,
- borderRadius: diameter / 2,
- overflow: 'hidden',
- },
- })
- )
+
+ return address
+ ? (
+ h('div', {
+ className: `${className} identicon`,
+ key: 'identicon-' + address,
+ style: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: diameter,
+ width: diameter,
+ borderRadius: diameter / 2,
+ overflow: 'hidden',
+ },
+ })
+ )
+ : (
+ h('img.balance-icon', {
+ src: '../images/eth_logo.svg',
+ style: {
+ height: diameter,
+ width: diameter,
+ borderRadius: diameter / 2,
+ },
+ })
+ )
}
IdenticonComponent.prototype.componentDidMount = function () {
diff --git a/ui/app/components/input-number.js b/ui/app/components/input-number.js
new file mode 100644
index 000000000..2824d77aa
--- /dev/null
+++ b/ui/app/components/input-number.js
@@ -0,0 +1,57 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+
+module.exports = InputNumber
+
+inherits(InputNumber, Component)
+function InputNumber () {
+ Component.call(this)
+
+ this.state = {
+ value: 0,
+ }
+
+ this.setValue = this.setValue.bind(this)
+}
+
+InputNumber.prototype.componentWillMount = function () {
+ const { initValue = 0 } = this.props
+
+ this.setState({ value: initValue })
+}
+
+InputNumber.prototype.setValue = function (newValue) {
+ const { fixed, min = -1, onChange } = this.props
+
+ if (fixed) newValue = Number(newValue.toFixed(4))
+
+ if (newValue >= min) {
+ this.setState({ value: newValue })
+ onChange(newValue)
+ }
+}
+
+InputNumber.prototype.render = function () {
+ const { unitLabel, step = 1, placeholder } = this.props
+ const { value } = this.state
+
+ return h('div.customize-gas-input-wrapper', {}, [
+ h('input.customize-gas-input', {
+ placeholder,
+ type: 'number',
+ value,
+ onChange: (e) => this.setValue(Number(e.target.value)),
+ }),
+ h('span.gas-tooltip-input-detail', {}, [unitLabel]),
+ h('div.gas-tooltip-input-arrows', {}, [
+ h('i.fa.fa-angle-up', {
+ onClick: () => this.setValue(value + step),
+ }),
+ h('i.fa.fa-angle-down', {
+ style: { cursor: 'pointer' },
+ onClick: () => this.setValue(value - step),
+ }),
+ ]),
+ ])
+}
diff --git a/ui/app/components/menu-droppo.js b/ui/app/components/menu-droppo.js
index 66ab18954..95b75f54c 100644
--- a/ui/app/components/menu-droppo.js
+++ b/ui/app/components/menu-droppo.js
@@ -13,21 +13,23 @@ function MenuDroppoComponent () {
}
MenuDroppoComponent.prototype.render = function () {
+ const { containerClassName = '' } = this.props
const speed = this.props.speed || '300ms'
const useCssTransition = this.props.useCssTransition
const zIndex = ('zIndex' in this.props) ? this.props.zIndex : 0
this.manageListeners()
- let style = this.props.style || {}
+ const style = this.props.style || {}
if (!('position' in style)) {
style.position = 'fixed'
}
style.zIndex = zIndex
return (
- h('.menu-droppo-container', {
+ h('div', {
style,
+ className: `.menu-droppo-container ${containerClassName}`,
}, [
h('style', `
.menu-droppo-enter {
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js
new file mode 100644
index 000000000..ec7e4b500
--- /dev/null
+++ b/ui/app/components/modals/account-details-modal.js
@@ -0,0 +1,82 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const { getSelectedIdentity, getSelectedAddress } = require('../../selectors')
+const genAccountLink = require('../../../lib/account-link.js')
+const Identicon = require('../identicon')
+const QrView = require('../qr-code')
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ address: state.metamask.selectedAddress,
+ selectedAddress: getSelectedAddress(state),
+ selectedIdentity: getSelectedIdentity(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
+ }
+}
+
+inherits(AccountDetailsModal, Component)
+function AccountDetailsModal () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsModal)
+
+// Not yet pixel perfect todos:
+ // fonts of qr-header and close button
+
+AccountDetailsModal.prototype.render = function () {
+ const { selectedIdentity, network } = this.props
+
+ return h('div', { style: { borderRadius: '4px' }}, [
+ h('div.account-details-modal-wrapper', [
+
+ h('div', [
+
+ // Needs a border; requires changes to svg
+ h(Identicon, {
+ address: selectedIdentity.address,
+ diameter: 64,
+ style: {},
+ }),
+
+ ]),
+
+ h('div.account-details-modal-close', {
+ onClick: this.props.hideModal,
+ }),
+
+ h(QrView, {
+ Qr: {
+ message: this.props.selectedIdentity.name,
+ data: this.props.selectedIdentity.address,
+ },
+ }),
+
+ // divider
+ h('div.account-details-modal-divider'),
+
+ h('button.btn-clear', {
+ onClick: () => {
+ const url = genAccountLink(selectedIdentity.address, network)
+ global.platform.openWindow({ url })
+ },
+ }, [ 'View account on Etherscan' ]),
+
+ // Holding on redesign for Export Private Key functionality
+ h('button.btn-clear', [ 'Export private key' ]),
+
+ ]),
+ ])
+}
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
new file mode 100644
index 000000000..79bbc798b
--- /dev/null
+++ b/ui/app/components/modals/buy-options-modal.js
@@ -0,0 +1,77 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ address: state.metamask.selectedAddress,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ toCoinbase: (address) => {
+ dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
+ },
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+inherits(BuyOptions, Component)
+function BuyOptions () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(BuyOptions)
+
+BuyOptions.prototype.render = function () {
+ return h('div', {}, [
+ h('div.buy-modal-content.transfers-subview', {
+ }, [
+ h('div.buy-modal-content-title-wrapper.flex-column.flex-center', {
+ style: {},
+ }, [
+ h('div.buy-modal-content-title', {
+ style: {},
+ }, 'Transfers'),
+ h('div', {}, 'How would you like to buy Ether?'),
+ ]),
+
+ h('div.buy-modal-content-options.flex-column.flex-center', {}, [
+
+ h('div.buy-modal-content-option', {
+ onClick: () => {
+ const { toCoinbase, address } = this.props
+ toCoinbase(address)
+ },
+ }, [
+ h('div.buy-modal-content-option-title', {}, 'Coinbase'),
+ h('div.buy-modal-content-option-subtitle', {}, 'Buy with Fiat'),
+ ]),
+
+ h('div.buy-modal-content-option', {}, [
+ h('div.buy-modal-content-option-title', {}, 'Shapeshift'),
+ h('div.buy-modal-content-option-subtitle', {}, 'Trade any digital asset for any other'),
+ ]),
+
+ h('div.buy-modal-content-option', {}, [
+ h('div.buy-modal-content-option-title', {}, 'Direct Deposit'),
+ h('div.buy-modal-content-option-subtitle', {}, 'Deposit from another account'),
+ ]),
+
+ ]),
+
+ h('button', {
+ style: {
+ background: 'white',
+ },
+ onClick: () => { this.props.hideModal() },
+ }, h('div.buy-modal-content-footer#buy-modal-content-footer-text', {}, 'Cancel')),
+ ]),
+ ])
+}
diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js
new file mode 100644
index 000000000..5c25ac245
--- /dev/null
+++ b/ui/app/components/modals/edit-account-name-modal.js
@@ -0,0 +1,76 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+const { getSelectedAccount } = require('../../selectors')
+
+function mapStateToProps (state) {
+ return {
+ selectedAccount: getSelectedAccount(state),
+ identity: state.appState.modal.modalState.identity,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ saveAccountLabel: (account, label) => {
+ dispatch(actions.saveAccountLabel(account, label))
+ },
+ }
+}
+
+inherits(EditAccountNameModal, Component)
+function EditAccountNameModal () {
+ Component.call(this)
+ this.state = {
+ inputText: '',
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(EditAccountNameModal)
+
+EditAccountNameModal.prototype.render = function () {
+ const { hideModal, saveAccountLabel, identity } = this.props
+
+ return h('div', {}, [
+ h('div.flex-column.edit-account-name-modal-content', {
+ }, [
+
+ h('div.edit-account-name-modal-cancel', {
+ onClick: () => {
+ hideModal()
+ },
+ }, [
+ h('i.fa.fa-times'),
+ ]),
+
+ h('div.edit-account-name-modal-title', {
+ }, ['Edit Account Name']),
+
+ h('input.edit-account-name-modal-input', {
+ placeholder: identity.name,
+ onChange: (event) => {
+ this.setState({ inputText: event.target.value })
+ },
+ value: this.state.inputText,
+ }, []),
+
+ h('button.btn-clear.edit-account-name-modal-save-button', {
+ onClick: () => {
+ if (this.state.inputText.length !== 0) {
+ saveAccountLabel(identity.address, this.state.inputText)
+ hideModal()
+ }
+ },
+ disabled: this.state.inputText.length === 0,
+ }, [
+ 'SAVE',
+ ]),
+
+ ]),
+ ])
+}
diff --git a/ui/app/components/modals/index.js b/ui/app/components/modals/index.js
new file mode 100644
index 000000000..1db1d33d4
--- /dev/null
+++ b/ui/app/components/modals/index.js
@@ -0,0 +1,5 @@
+const Modal = require('./modal')
+
+module.exports = {
+ Modal,
+}
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
new file mode 100644
index 000000000..477dcbe86
--- /dev/null
+++ b/ui/app/components/modals/modal.js
@@ -0,0 +1,162 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const FadeModal = require('boron').FadeModal
+const actions = require('../../actions')
+const isMobileView = require('../../../lib/is-mobile-view')
+const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification')
+
+// Modal Components
+const BuyOptions = require('./buy-options-modal')
+const AccountDetailsModal = require('./account-details-modal')
+const EditAccountNameModal = require('./edit-account-name-modal')
+const NewAccountModal = require('./new-account-modal')
+
+const MODALS = {
+ BUY: {
+ contents: [
+ h(BuyOptions, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ },
+ laptopModalStyle: {
+ width: '66%',
+ top: 'calc(30% + 10px)',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ },
+ },
+
+ EDIT_ACCOUNT_NAME: {
+ contents: [
+ h(EditAccountNameModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '48vh' : '36.5vh',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ },
+ laptopModalStyle: {
+ width: '375px',
+ top: 'calc(30% + 10px)',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ },
+ },
+
+ ACCOUNT_DETAILS: {
+ contents: [
+ h(AccountDetailsModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ borderRadius: '4px',
+ },
+ laptopModalStyle: {
+ width: '360px',
+ top: 'calc(33% + 45px)',
+ boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px',
+ borderRadius: '4px',
+ },
+ contentStyle: {
+ borderRadius: '4px',
+ }
+ },
+
+ NEW_ACCOUNT: {
+ contents: [
+ h(NewAccountModal, {}, []),
+ ],
+ mobileModalStyle: {
+ width: '95%',
+ top: isPopupOrNotification() === 'popup' ? '52vh' : '36.5vh',
+ },
+ laptopModalStyle: {
+ width: '449px',
+ top: 'calc(33% + 45px)',
+ },
+ },
+
+ DEFAULT: {
+ contents: [],
+ mobileModalStyle: {},
+ laptopModalStyle: {},
+ },
+}
+
+const BACKDROPSTYLE = {
+ backgroundColor: 'rgba(245, 245, 245, 0.85)',
+}
+
+function mapStateToProps (state) {
+ return {
+ active: state.appState.modal.open,
+ modalState: state.appState.modal.modalState,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+// Global Modal Component
+inherits(Modal, Component)
+function Modal () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(Modal)
+
+Modal.prototype.render = function () {
+ const modal = MODALS[this.props.modalState.name || 'DEFAULT']
+
+ const children = modal.contents
+ const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle']
+ const contentStyle = modal.contentStyle || {};
+
+ return h(FadeModal,
+ {
+ className: 'modal',
+ keyboard: false,
+ onHide: () => { this.onHide() },
+ ref: (ref) => {
+ this.modalRef = ref
+ },
+ modalStyle,
+ contentStyle,
+ backdropStyle: BACKDROPSTYLE,
+ },
+ children,
+ )
+}
+
+Modal.prototype.componentWillReceiveProps = function (nextProps) {
+ if (nextProps.active) {
+ this.show()
+ } else if (this.props.active) {
+ this.hide()
+ }
+}
+
+Modal.prototype.onHide = function () {
+ if (this.props.onHideCallback) {
+ this.props.onHideCallback()
+ }
+ this.props.hideModal()
+}
+
+Modal.prototype.hide = function () {
+ this.modalRef.hide()
+}
+
+Modal.prototype.show = function () {
+ this.modalRef.show()
+}
diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/modals/new-account-modal.js
new file mode 100644
index 000000000..910f3c0ca
--- /dev/null
+++ b/ui/app/components/modals/new-account-modal.js
@@ -0,0 +1,75 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const connect = require('react-redux').connect
+const actions = require('../../actions')
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ address: state.metamask.selectedAddress,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ toCoinbase: (address) => {
+ dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
+ },
+ hideModal: () => {
+ dispatch(actions.hideModal())
+ },
+ createAccount: () => {
+ dispatch(actions.addNewAccount())
+ dispatch(actions.hideModal())
+ },
+ }
+}
+
+inherits(NewAccountModal, Component)
+function NewAccountModal () {
+ Component.call(this)
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountModal)
+
+NewAccountModal.prototype.render = function () {
+ return h('div', {}, [
+ h('div.new-account-modal-wrapper', {
+ }, [
+ h('div.new-account-modal-header', {}, [
+ 'New Account',
+ ]),
+
+ h('div.modal-close-x', {
+ onClick: this.props.hideModal,
+ }),
+
+ h('div.new-account-modal-content', {}, [
+ 'Account Name',
+ ]),
+
+ h('div.new-account-input-wrapper', {}, [
+ h('input.new-account-input', {
+ placeholder: 'E.g. My new account',
+ }, []),
+ ]),
+
+ h('div.new-account-modal-content.after-input', {}, [
+ 'or',
+ ]),
+
+ h('div.new-account-modal-content.after-input', {}, [
+ 'Import an account',
+ ]),
+
+ h('div.new-account-modal-content.button', {}, [
+ h('button.btn-clear', {
+ onClick: this.props.createAccount
+ }, [
+ 'SAVE',
+ ]),
+ ]),
+ ]),
+ ])
+}
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index 0dbe37cdd..8424a479a 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon')
module.exports = Network
@@ -60,7 +61,7 @@ Network.prototype.render = function () {
}
return (
- h('#network_component.pointer', {
+ h('.network-component.pointer', {
title: hoverText,
onClick: (event) => this.props.onClick(event),
}, [
@@ -68,7 +69,10 @@ Network.prototype.render = function () {
switch (iconName) {
case 'ethereum-network':
return h('.network-indicator', [
- h('.menu-icon.diamond'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#038789', // $blue-lagoon
+ nonSelectBackgroundColor: '#15afb2'
+ }),
h('.network-name', {
style: {
color: '#039396',
@@ -78,7 +82,10 @@ Network.prototype.render = function () {
])
case 'ropsten-test-network':
return h('.network-indicator', [
- h('.menu-icon.red-dot'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#e91550', // $crimson
+ nonSelectBackgroundColor: '#ec2c50',
+ }),
h('.network-name', {
style: {
color: '#ff6666',
@@ -88,7 +95,10 @@ Network.prototype.render = function () {
])
case 'kovan-test-network':
return h('.network-indicator', [
- h('.menu-icon.hollow-diamond'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#690496', // $purple
+ nonSelectBackgroundColor: '#b039f3',
+ }),
h('.network-name', {
style: {
color: '#690496',
@@ -98,7 +108,10 @@ Network.prototype.render = function () {
])
case 'rinkeby-test-network':
return h('.network-indicator', [
- h('.menu-icon.golden-square'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#ebb33f', // $tulip-tree
+ nonSelectBackgroundColor: '#ecb23e',
+ }),
h('.network-name', {
style: {
color: '#e7a218',
diff --git a/ui/app/components/pending-tx.js b/ui/app/components/pending-tx.js
index c3350fcc1..18b622925 100644
--- a/ui/app/components/pending-tx.js
+++ b/ui/app/components/pending-tx.js
@@ -1,26 +1,57 @@
const Component = require('react').Component
+const { connect } = require('react-redux')
const h = require('react-hyperscript')
+const abi = require('human-standard-token-abi')
+const abiDecoder = require('abi-decoder')
+abiDecoder.addABI(abi)
const inherits = require('util').inherits
const actions = require('../actions')
const clone = require('clone')
+const FiatValue = require('./fiat-value')
+const Identicon = require('./identicon')
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const hexToBn = require('../../../app/scripts/lib/hex-to-bn')
const util = require('../util')
-const MiniAccountPanel = require('./mini-account-panel')
-const Copyable = require('./copyable')
-const EthBalance = require('./eth-balance')
-const addressSummary = util.addressSummary
-const nameForAddress = require('../../lib/contract-namer')
-const BNInput = require('./bn-as-decimal-input')
+const { conversionUtil } = require('../conversion-util')
const MIN_GAS_PRICE_GWEI_BN = new BN(1)
const GWEI_FACTOR = new BN(1e9)
const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR)
-const MIN_GAS_LIMIT_BN = new BN(21000)
-module.exports = PendingTx
+// Next: create separate react components
+// roughly 5 components:
+// heroIcon
+// numericDisplay (contains symbol + currency)
+// divider
+// contentBox
+// actionButtons
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(PendingTx)
+
+function mapStateToProps (state) {
+ const {
+ conversionRate,
+ identities,
+ } = state.metamask
+ const accounts = state.metamask.accounts
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
+ return {
+ conversionRate,
+ identities,
+ selectedAddress,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('USD')),
+ backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
+ cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })),
+ }
+}
+
inherits(PendingTx, Component)
function PendingTx () {
Component.call(this)
@@ -29,412 +60,444 @@ function PendingTx () {
txData: null,
submitting: false,
}
+ this.onSubmit = this.onSubmit.bind(this)
}
-PendingTx.prototype.render = function () {
- const props = this.props
- const { currentCurrency, blockGasLimit } = props
+PendingTx.prototype.componentWillMount = function () {
+ this.props.setCurrentCurrencyToUSD()
+}
- const conversionRate = props.conversionRate
+PendingTx.prototype.getTotal = function () {
+ const { conversionRate } = this.props
const txMeta = this.gatherTxMeta()
const txParams = txMeta.txParams || {}
+ const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
+ const { params = [] } = decodedData || {}
+ const { name, value } = params[1] || {}
+ const amountBn = name === '_value'
+ ? value
+ : txParams.value
+
+ if (name === '_value') {
+ const token = util.getContractAtAddress(txParams.to)
+ token.symbol().then(symbol => console.log({symbol}))
+ console.log({txParams, txMeta, decodedData, token})
+ const USD = conversionUtil(amountBn, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency: 'USD',
+ numberOfDecimals: 2,
+ fromDenomination: 'WEI',
+ conversionRate,
+ })
+ const ETH = conversionUtil(amountBn, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency: 'ETH',
+ fromDenomination: 'WEI',
+ conversionRate,
+ numberOfDecimals: 6,
+ })
+ return {
+ USD,
+ ETH,
+ }
+ } else {
+ const USD = conversionUtil(amountBn, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency: 'USD',
+ numberOfDecimals: 2,
+ fromDenomination: 'WEI',
+ conversionRate,
+ })
+ const ETH = conversionUtil(amountBn, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency: 'ETH',
+ fromDenomination: 'WEI',
+ conversionRate,
+ numberOfDecimals: 6,
+ })
- // Account Details
- const address = txParams.from || props.selectedAddress
- const identity = props.identities[address] || { address: address }
- const account = props.accounts[address]
- const balance = account ? account.balance : '0x0'
+ return {
+ USD,
+ ETH,
+ }
+ }
- // recipient check
- const isValidAddress = !txParams.to || util.isValidAddress(txParams.to)
+}
+
+PendingTx.prototype.getGasFee = function () {
+ const { conversionRate } = this.props
+ const txMeta = this.gatherTxMeta()
+ const txParams = txMeta.txParams || {}
// Gas
const gas = txParams.gas
const gasBn = hexToBn(gas)
- const gasLimit = new BN(parseInt(blockGasLimit))
- const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
- const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
- const safeGasLimit = safeGasLimitBN.toString(10)
+
+ // From latest master
+// const gasLimit = new BN(parseInt(blockGasLimit))
+// const safeGasLimitBN = this.bnMultiplyByFraction(gasLimit, 19, 20)
+// const saferGasLimitBN = this.bnMultiplyByFraction(gasLimit, 18, 20)
+// const safeGasLimit = safeGasLimitBN.toString(10)
// Gas Price
const gasPrice = txParams.gasPrice || MIN_GAS_PRICE_BN.toString(16)
const gasPriceBn = hexToBn(gasPrice)
const txFeeBn = gasBn.mul(gasPriceBn)
- const valueBn = hexToBn(txParams.value)
- const maxCost = txFeeBn.add(valueBn)
- const dataLength = txParams.data ? (txParams.data.length - 2) / 2 : 0
+ const USD = conversionUtil(txFeeBn, {
+ fromNumericBase: 'BN',
+ toNumericBase: 'dec',
+ fromDenomination: 'WEI',
+ fromCurrency: 'ETH',
+ toCurrency: 'USD',
+ numberOfDecimals: 2,
+ conversionRate,
+ })
+ const ETH = conversionUtil(txFeeBn, {
+ fromNumericBase: 'BN',
+ toNumericBase: 'dec',
+ fromDenomination: 'WEI',
+ fromCurrency: 'ETH',
+ toCurrency: 'ETH',
+ numberOfDecimals: 6,
+ conversionRate,
+ })
+
+ return {
+ USD,
+ ETH,
+ }
+}
+
+PendingTx.prototype.getData = function () {
+ const { identities } = this.props
+ const txMeta = this.gatherTxMeta()
+ const txParams = txMeta.txParams || {}
+ const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
+ const { name, params = [] } = decodedData || {}
+ const { type, value } = params[0] || {}
+ const { USD: gasFeeInUSD, ETH: gasFeeInETH } = this.getGasFee()
+ const { USD: totalInUSD, ETH: totalInETH } = this.getTotal()
+
+ if (name === 'transfer' && type === 'address') {
+ return {
+ from: {
+ address: txParams.from,
+ name: identities[txParams.from].name,
+ },
+ to: {
+ address: value,
+ name: identities[value] ? identities[value].name : 'New Recipient',
+ },
+ memo: txParams.memo || '',
+ gasFeeInUSD,
+ gasFeeInETH,
+ totalInUSD,
+ totalInETH,
+ }
+ } else {
+ return {
+ from: {
+ address: txParams.from,
+ name: identities[txParams.from].name,
+ },
+ to: {
+ address: txParams.to,
+ name: identities[txParams.to] ? identities[txParams.to].name : 'New Recipient',
+ },
+ memo: txParams.memo || '',
+ gasFeeInUSD,
+ gasFeeInETH,
+ totalInUSD,
+ totalInETH,
+ }
+ }
+}
+
+PendingTx.prototype.render = function () {
+ const { backToAccountDetail, selectedAddress } = this.props
+ const txMeta = this.gatherTxMeta()
+ const txParams = txMeta.txParams || {}
- const balanceBn = hexToBn(balance)
- const insufficientBalance = balanceBn.lt(maxCost)
- const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
- const gasLimitSpecified = txMeta.gasLimitSpecified
- const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
- const showRejectAll = props.unconfTxListLength > 1
+ // recipient check
+ // const isValidAddress = !txParams.to || util.isValidAddress(txParams.to)
+
+ const {
+ from: {
+ address: fromAddress,
+ name: fromName,
+ },
+ to: {
+ address: toAddress,
+ name: toName,
+ },
+ memo,
+ gasFeeInUSD,
+ gasFeeInETH,
+ totalInUSD,
+ totalInETH,
+ } = this.getData()
+
+ // This is from the latest master
+ // It handles some of the errors that we are not currently handling
+ // Leaving as comments fo reference
+
+ // const balanceBn = hexToBn(balance)
+ // const insufficientBalance = balanceBn.lt(maxCost)
+ // const buyDisabled = insufficientBalance || !this.state.valid || !isValidAddress || this.state.submitting
+ // const showRejectAll = props.unconfTxListLength > 1
+// const dangerousGasLimit = gasBn.gte(saferGasLimitBN)
+// const gasLimitSpecified = txMeta.gasLimitSpecified
this.inputs = []
return (
-
- h('div', {
- key: txMeta.id,
+ h('div.flex-column.flex-grow.confirm-screen-container', {
+ style: { minWidth: '355px' },
}, [
+ // Main Send token Card
+ h('div.confirm-screen-wrapper.flex-column.flex-grow', [
+ h('h3.flex-center.confirm-screen-header', [
+ h('button.confirm-screen-back-button', {
+ onClick: () => backToAccountDetail(selectedAddress),
+ }, 'BACK'),
+ h('div.confirm-screen-title', 'Confirm Transaction'),
+ ]),
+ h('div.flex-row.flex-center.confirm-screen-identicons', [
+ h('div.confirm-screen-account-wrapper', [
+ h(
+ Identicon,
+ {
+ address: fromAddress,
+ diameter: 100,
+ },
+ ),
+ h('span.confirm-screen-account-name', fromName),
+ h('span.confirm-screen-account-number', fromAddress.slice(fromAddress.length - 4)),
+ ]),
+ h('i.fa.fa-arrow-right.fa-lg'),
+ h('div.confirm-screen-account-wrapper', [
+ h(
+ Identicon,
+ {
+ address: txParams.to,
+ diameter: 100,
+ },
+ ),
+ h('span.confirm-screen-account-name', toName),
+ h('span.confirm-screen-account-number', toAddress.slice(toAddress.length - 4)),
+ ]),
+ ]),
- h('form#pending-tx-form', {
- onSubmit: this.onSubmit.bind(this),
+ h('h3.flex-center.confirm-screen-sending-to-message', {
+ style: {
+ textAlign: 'center',
+ fontSize: '16px',
+ },
+ }, [
+ `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
+ ]),
- }, [
+ h('h3.flex-center.confirm-screen-send-amount', [`$${totalInUSD}`]),
+ h('h3.flex-center.confirm-screen-send-amount-currency', [ 'USD' ]),
+ h('div.flex-center.confirm-memo-wrapper', [
+ h('h3.confirm-screen-send-memo', [ memo ]),
+ ]),
- // tx info
- h('div', [
-
- h('.flex-row.flex-center', {
- style: {
- maxWidth: '100%',
- },
- }, [
-
- h(MiniAccountPanel, {
- imageSeed: address,
- picOrder: 'right',
- }, [
- h('span.font-small', {
- style: {
- fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
- },
- }, identity.name),
-
- h(Copyable, {
- value: ethUtil.toChecksumAddress(address),
- }, [
- h('span.font-small', {
- style: {
- fontFamily: 'Montserrat Light, Montserrat, sans-serif',
- },
- }, addressSummary(address, 6, 4, false)),
- ]),
-
- h('span.font-small', {
- style: {
- fontFamily: 'Montserrat Light, Montserrat, sans-serif',
- },
- }, [
- h(EthBalance, {
- value: balance,
- conversionRate,
- currentCurrency,
- inline: true,
- labelColor: '#F7861C',
- }),
- ]),
+ h('div.confirm-screen-rows', [
+ h('section.flex-row.flex-center.confirm-screen-row', [
+ h('span.confirm-screen-label.confirm-screen-section-column', [ 'From' ]),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', fromName),
+ h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
]),
-
- forwardCarrat(),
-
- this.miniAccountPanelForRecipient(),
]),
- h('style', `
- .table-box {
- margin: 7px 0px 0px 0px;
- width: 100%;
- }
- .table-box .row {
- margin: 0px;
- background: rgb(236,236,236);
- display: flex;
- justify-content: space-between;
- font-family: Montserrat Light, sans-serif;
- font-size: 13px;
- padding: 5px 25px;
- }
- .table-box .row .value {
- font-family: Montserrat Regular;
- }
- `),
-
- h('.table-box', [
-
- // Ether Value
- // Currently not customizable, but easily modified
- // in the way that gas and gasLimit currently are.
- h('.row', [
- h('.cell.label', 'Amount'),
- h(EthBalance, { value: txParams.value, currentCurrency, conversionRate }),
+ h('section.flex-row.flex-center.confirm-screen-row', [
+ h('span.confirm-screen-label.confirm-screen-section-column', [ 'To' ]),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', toName),
+ h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
]),
+ ]),
- // Gas Limit (customizable)
- h('.cell.row', [
- h('.cell.label', 'Gas Limit'),
- h('.cell.value', {
- }, [
- h(BNInput, {
- name: 'Gas Limit',
- value: gasBn,
- precision: 0,
- scale: 0,
- // The hard lower limit for gas.
- min: MIN_GAS_LIMIT_BN.toString(10),
- max: safeGasLimit,
- suffix: 'UNITS',
- style: {
- position: 'relative',
- top: '5px',
- },
- onChange: this.gasLimitChanged.bind(this),
-
- ref: (hexInput) => { this.inputs.push(hexInput) },
- }),
- ]),
- ]),
+ h('section.flex-row.flex-center.confirm-screen-row', [
+ h('span.confirm-screen-label.confirm-screen-section-column', [ 'Gas Fee' ]),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', `$${gasFeeInUSD} USD`),
- // Gas Price (customizable)
- h('.cell.row', [
- h('.cell.label', 'Gas Price'),
- h('.cell.value', {
- }, [
- h(BNInput, {
- name: 'Gas Price',
- value: gasPriceBn,
- precision: 9,
- scale: 9,
- suffix: 'GWEI',
- min: MIN_GAS_PRICE_GWEI_BN.toString(10),
- style: {
- position: 'relative',
- top: '5px',
- },
- onChange: this.gasPriceChanged.bind(this),
- ref: (hexInput) => { this.inputs.push(hexInput) },
- }),
- ]),
+ h('div.confirm-screen-row-detail', `${gasFeeInETH} ETH`),
]),
+ ]),
- // Max Transaction Fee (calculated)
- h('.cell.row', [
- h('.cell.label', 'Max Transaction Fee'),
- h(EthBalance, { value: txFeeBn.toString(16), currentCurrency, conversionRate }),
- ]),
- h('.cell.row', {
- style: {
- fontFamily: 'Montserrat Regular',
- background: 'white',
- padding: '10px 25px',
- },
- }, [
- h('.cell.label', 'Max Total'),
- h('.cell.value', {
- style: {
- display: 'flex',
- alignItems: 'center',
- },
- }, [
- h(EthBalance, {
- value: maxCost.toString(16),
- currentCurrency,
- conversionRate,
- inline: true,
- labelColor: 'black',
- fontSize: '16px',
- }),
- ]),
+ h('section.flex-row.flex-center.confirm-screen-total-box ', [
+ h('div.confirm-screen-section-column', [
+ h('span.confirm-screen-label', [ 'Total ' ]),
+ h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]),
]),
- // Data size row:
- h('.cell.row', {
- style: {
- background: '#f7f7f7',
- paddingBottom: '0px',
- },
- }, [
- h('.cell.label'),
- h('.cell.value', {
- style: {
- fontFamily: 'Montserrat Light',
- fontSize: '11px',
- },
- }, `Data included: ${dataLength} bytes`),
+ h('div.confirm-screen-section-column', [
+ h('div.confirm-screen-row-info', `$${totalInUSD} USD`),
+ h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
]),
- ]), // End of Table
-
- ]),
-
- h('style', `
- .conf-buttons button {
- margin-left: 10px;
- text-transform: uppercase;
- }
- `),
- h('.cell.row', {
- style: {
- textAlign: 'center',
- },
- }, [
- txMeta.simulationFails ?
- h('.error', {
- style: {
- fontSize: '0.9em',
- },
- }, 'Transaction Error. Exception thrown in contract code.')
- : null,
-
- !isValidAddress ?
- h('.error', {
- style: {
- fontSize: '0.9em',
- },
- }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
- : null,
-
- insufficientBalance ?
- h('span.error', {
- style: {
- fontSize: '0.9em',
- },
- }, 'Insufficient balance for transaction')
- : null,
-
- (dangerousGasLimit && !gasLimitSpecified) ?
- h('span.error', {
- style: {
- fontSize: '0.9em',
- },
- }, 'Gas limit set dangerously high. Approving this transaction is likely to fail.')
- : null,
+ ]),
]),
-
- // send + cancel
- h('.flex-row.flex-space-around.conf-buttons', {
- style: {
- display: 'flex',
- justifyContent: 'flex-end',
- margin: '14px 25px',
- },
- }, [
- h('button', {
- onClick: (event) => {
- this.resetGasFields()
- event.preventDefault()
- },
- }, 'Reset'),
-
- // Accept Button or Buy Button
- insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
- h('input.confirm.btn-green', {
- type: 'submit',
- value: 'SUBMIT',
- style: { marginLeft: '10px' },
- disabled: buyDisabled,
- }),
-
- h('button.cancel.btn-red', {
- onClick: props.cancelTransaction,
- }, 'Reject'),
- ]),
- showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
- style: {
- display: 'flex',
- justifyContent: 'flex-end',
- margin: '14px 25px',
- },
- }, [
- h('button.cancel.btn-red', {
- onClick: props.cancelAllTransactions,
- }, 'Reject All'),
- ]) : null,
+// These are latest errors handling from master
+// Leaving as comments as reference when we start implementing error handling
+// h('style', `
+// .conf-buttons button {
+// margin-left: 10px;
+// text-transform: uppercase;
+// }
+// `),
+
+// txMeta.simulationFails ?
+// h('.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Transaction Error. Exception thrown in contract code.')
+// : null,
+
+// !isValidAddress ?
+// h('.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Recipient address is invalid. Sending this transaction will result in a loss of ETH.')
+// : null,
+
+// insufficientBalance ?
+// h('span.error', {
+// style: {
+// marginLeft: 50,
+// fontSize: '0.9em',
+// },
+// }, 'Insufficient balance for transaction')
+// : null,
+
+// // send + cancel
+// h('.flex-row.flex-space-around.conf-buttons', {
+// style: {
+// display: 'flex',
+// justifyContent: 'flex-end',
+// margin: '14px 25px',
+// },
+// }, [
+// h('button', {
+// onClick: (event) => {
+// this.resetGasFields()
+// event.preventDefault()
+// },
+// }, 'Reset'),
+
+// // Accept Button or Buy Button
+// insufficientBalance ? h('button.btn-green', { onClick: props.buyEth }, 'Buy Ether') :
+// h('input.confirm.btn-green', {
+// type: 'submit',
+// value: 'SUBMIT',
+// style: { marginLeft: '10px' },
+// disabled: buyDisabled,
+// }),
+
+// h('button.cancel.btn-red', {
+// onClick: props.cancelTransaction,
+// }, 'Reject'),
+// ]),
+// showRejectAll ? h('.flex-row.flex-space-around.conf-buttons', {
+// style: {
+// display: 'flex',
+// justifyContent: 'flex-end',
+// margin: '14px 25px',
+// },
+// }, [
+// h('button.cancel.btn-red', {
+// onClick: props.cancelAllTransactions,
+// }, 'Reject All'),
+// ]) : null,
+// ]),
+// ])
+// )
+// }
]),
- ])
- )
-}
-
-PendingTx.prototype.miniAccountPanelForRecipient = function () {
- const props = this.props
- const txData = props.txData
- const txParams = txData.txParams || {}
- const isContractDeploy = !('to' in txParams)
-
- // If it's not a contract deploy, send to the account
- if (!isContractDeploy) {
- return h(MiniAccountPanel, {
- imageSeed: txParams.to,
- picOrder: 'left',
- }, [
- h('span.font-small', {
- style: {
- fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
- },
- }, nameForAddress(txParams.to, props.identities)),
-
- h(Copyable, {
- value: ethUtil.toChecksumAddress(txParams.to),
+ h('form#pending-tx-form.flex-column.flex-center', {
+ onSubmit: this.onSubmit,
}, [
- h('span.font-small', {
- style: {
- fontFamily: 'Montserrat Light, Montserrat, sans-serif',
- },
- }, addressSummary(txParams.to, 6, 4, false)),
+ // Reset Button
+ // h('button', {
+ // onClick: (event) => {
+ // this.resetGasFields()
+ // event.preventDefault()
+ // },
+ // }, 'Reset'),
+
+ // Accept Button
+ h('button.confirm-screen-confirm-button', ['CONFIRM']),
+
+ // Cancel Button
+ h('div.cancel.btn-light.confirm-screen-cancel-button', {
+ onClick: (event) => this.cancel(event, txMeta),
+ }, 'CANCEL'),
]),
-
])
- } else {
- return h(MiniAccountPanel, {
- picOrder: 'left',
- }, [
-
- h('span.font-small', {
- style: {
- fontFamily: 'Montserrat Bold, Montserrat, sans-serif',
- },
- }, 'New Contract'),
-
- ])
- }
-}
-
-PendingTx.prototype.gasPriceChanged = function (newBN, valid) {
- log.info(`Gas price changed to: ${newBN.toString(10)}`)
- const txMeta = this.gatherTxMeta()
- txMeta.txParams.gasPrice = '0x' + newBN.toString('hex')
- this.setState({
- txData: clone(txMeta),
- valid,
- })
-}
-
-PendingTx.prototype.gasLimitChanged = function (newBN, valid) {
- log.info(`Gas limit changed to ${newBN.toString(10)}`)
- const txMeta = this.gatherTxMeta()
- txMeta.txParams.gas = '0x' + newBN.toString('hex')
- this.setState({
- txData: clone(txMeta),
- valid,
- })
+ )
}
-PendingTx.prototype.resetGasFields = function () {
- log.debug(`pending-tx resetGasFields`)
-
- this.inputs.forEach((hexInput) => {
- if (hexInput) {
- hexInput.setValid()
- }
- })
-
- this.setState({
- txData: null,
- valid: true,
- })
-}
+// PendingTx.prototype.gasPriceChanged = function (newBN, valid) {
+// log.info(`Gas price changed to: ${newBN.toString(10)}`)
+// const txMeta = this.gatherTxMeta()
+// txMeta.txParams.gasPrice = '0x' + newBN.toString('hex')
+// this.setState({
+// txData: clone(txMeta),
+// valid,
+// })
+// }
+
+// PendingTx.prototype.gasLimitChanged = function (newBN, valid) {
+// log.info(`Gas limit changed to ${newBN.toString(10)}`)
+// const txMeta = this.gatherTxMeta()
+// txMeta.txParams.gas = '0x' + newBN.toString('hex')
+// this.setState({
+// txData: clone(txMeta),
+// valid,
+// })
+// }
+
+// PendingTx.prototype.resetGasFields = function () {
+// log.debug(`pending-tx resetGasFields`)
+
+// this.inputs.forEach((hexInput) => {
+// if (hexInput) {
+// hexInput.setValid()
+// }
+// })
+
+// this.setState({
+// txData: null,
+// valid: true,
+// })
+// }
PendingTx.prototype.onSubmit = function (event) {
event.preventDefault()
const txMeta = this.gatherTxMeta()
const valid = this.checkValidity()
this.setState({ valid, submitting: true })
+
if (valid && this.verifyGasParams()) {
this.props.sendTransaction(txMeta, event)
} else {
@@ -443,6 +506,11 @@ PendingTx.prototype.onSubmit = function (event) {
}
}
+PendingTx.prototype.cancel = function (event, txMeta) {
+ event.preventDefault()
+ this.props.cancelTransaction(txMeta)
+}
+
PendingTx.prototype.checkValidity = function () {
const form = this.getFormEl()
const valid = form.checkValidity()
@@ -460,12 +528,11 @@ PendingTx.prototype.getFormEl = function () {
// After a customizable state value has been updated,
PendingTx.prototype.gatherTxMeta = function () {
- log.debug(`pending-tx gatherTxMeta`)
const props = this.props
const state = this.state
const txData = clone(state.txData) || clone(props.txData)
- log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
+ // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`)
return txData
}
@@ -487,15 +554,3 @@ PendingTx.prototype.bnMultiplyByFraction = function (targetBN, numerator, denomi
const denomBN = new BN(denominator)
return targetBN.mul(numBN).div(denomBN)
}
-
-function forwardCarrat () {
- return (
- h('img', {
- src: 'images/forward-carrat.svg',
- style: {
- padding: '5px 6px 0px 10px',
- height: '37px',
- },
- })
- )
-}
diff --git a/ui/app/components/qr-code.js b/ui/app/components/qr-code.js
index 06b9aed9b..4257c1a15 100644
--- a/ui/app/components/qr-code.js
+++ b/ui/app/components/qr-code.js
@@ -4,13 +4,12 @@ const qrCode = require('qrcode-npm').qrcode
const inherits = require('util').inherits
const connect = require('react-redux').connect
const isHexPrefixed = require('ethereumjs-util').isHexPrefixed
-const CopyButton = require('./copyButton')
module.exports = connect(mapStateToProps)(QrCodeView)
function mapStateToProps (state) {
return {
- Qr: state.appState.Qr,
+ // Qr code is not fetched from state. 'message' and 'data' props are passed instead.
buyView: state.appState.buyView,
warning: state.appState.warning,
}
@@ -29,45 +28,35 @@ QrCodeView.prototype.render = function () {
const qrImage = qrCode(4, 'M')
qrImage.addData(address)
qrImage.make()
- return h('.main-container.flex-column', {
- key: 'qr',
+ return h('.div.flex-column.flex-center', {
style: {
- justifyContent: 'center',
- paddingBottom: '45px',
- paddingLeft: '45px',
- paddingRight: '45px',
- alignItems: 'center',
},
}, [
Array.isArray(Qr.message) ? h('.message-container', this.renderMultiMessage()) : h('.qr-header', Qr.message),
this.props.warning ? this.props.warning && h('span.error.flex-center', {
style: {
- textAlign: 'center',
- width: '229px',
- height: '82px',
},
},
this.props.warning) : null,
- h('#qr-container.flex-column', {
- style: {
- marginTop: '25px',
- marginBottom: '15px',
- },
+ h('.div.qr-wrapper', {
+ style: {},
dangerouslySetInnerHTML: {
__html: qrImage.createTableTag(4),
},
}),
- h('.flex-row', [
- h('h3.ellip-address', {
+ h('.div.ellip-address-wrapper', [
+ h('input.qr-ellip-address', {
style: {
width: '247px',
},
- }, Qr.data),
- h(CopyButton, {
value: Qr.data,
+ readonly: true,
}),
+ // h(CopyButton, {
+ // value: Qr.data,
+ // }),
]),
])
}
diff --git a/ui/app/components/send-token/index.js b/ui/app/components/send-token/index.js
new file mode 100644
index 000000000..72fb593be
--- /dev/null
+++ b/ui/app/components/send-token/index.js
@@ -0,0 +1,365 @@
+const Component = require('react').Component
+const connect = require('react-redux').connect
+const h = require('react-hyperscript')
+const { addHexPrefix } = require('ethereumjs-util')
+const classnames = require('classnames')
+const inherits = require('util').inherits
+const actions = require('../../actions')
+const selectors = require('../../selectors')
+
+// const BalanceComponent = require('./balance-component')
+const Identicon = require('../identicon')
+const TokenBalance = require('../token-balance')
+const CurrencyToggle = require('../send/currency-toggle')
+const GasTooltip = require('../send/gas-tooltip')
+const GasFeeDisplay = require('../send/gas-fee-display')
+
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(SendTokenScreen)
+
+function mapStateToProps (state) {
+ // const sidebarOpen = state.appState.sidebarOpen
+
+ const identities = state.metamask.identities
+ const addressBook = state.metamask.addressBook
+ const conversionRate = state.metamask.conversionRate
+ const currentBlockGasLimit = state.metamask.currentBlockGasLimit
+ const accounts = state.metamask.accounts
+ // const network = state.metamask.network
+ const selectedTokenAddress = state.metamask.selectedTokenAddress
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
+ const selectedToken = selectors.getSelectedToken(state)
+ const tokenExchangeRates = state.metamask.tokenExchangeRates
+ const pair = `${selectedToken.symbol.toLowerCase()}_eth`
+ const { rate: tokenExchangeRate = 0 } = tokenExchangeRates[pair] || {}
+ // const checksumAddress = selectedAddress && ethUtil.toChecksumAddress(selectedAddress)
+ // const identity = identities[selectedAddress]
+ return {
+ // sidebarOpen,
+ selectedAddress,
+ // checksumAddress,
+ selectedTokenAddress,
+ identities,
+ addressBook,
+ conversionRate,
+ tokenExchangeRate,
+ currentBlockGasLimit,
+ selectedToken,
+ // selectedToken: selectors.getSelectedToken(state),
+ // identity,
+ // network,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ backToAccountDetail: address => dispatch(actions.backToAccountDetail(address)),
+ hideWarning: () => dispatch(actions.hideWarning()),
+ addToAddressBook: (recipient, nickname) => dispatch(
+ actions.addToAddressBook(recipient, nickname)
+ ),
+ signTx: txParams => dispatch(actions.signTx(txParams)),
+ signTokenTx: (tokenAddress, toAddress, amount, txData) => (
+ dispatch(actions.signTokenTx(tokenAddress, toAddress, amount, txData))
+ ),
+ updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
+ // showSidebar: () => { dispatch(actions.showSidebar()) },
+ // hideSidebar: () => { dispatch(actions.hideSidebar()) },
+ // showModal: (payload) => { dispatch(actions.showModal(payload)) },
+ // showSendPage: () => { dispatch(actions.showSendPage()) },
+ // showSendTokenPage: () => { dispatch(actions.showSendTokenPage()) },
+ }
+}
+
+inherits(SendTokenScreen, Component)
+function SendTokenScreen () {
+ Component.call(this)
+ this.state = {
+ to: '',
+ amount: '',
+ selectedCurrency: 'USD',
+ isGasTooltipOpen: false,
+ gasPrice: '0x5d21dba00',
+ gasLimit: '0x7b0d',
+ errors: {},
+ }
+}
+
+SendTokenScreen.prototype.componentWillMount = function () {
+ const {
+ updateTokenExchangeRate,
+ selectedToken: { symbol },
+ } = this.props
+
+ updateTokenExchangeRate(symbol)
+}
+
+SendTokenScreen.prototype.validate = function () {
+ const {
+ to,
+ amount: stringAmount,
+ gasPrice: hexGasPrice,
+ gasLimit: hexGasLimit,
+ } = this.state
+
+ const gasPrice = parseInt(hexGasPrice, 16)
+ const gasLimit = parseInt(hexGasLimit, 16) / 1000000000
+ const amount = Number(stringAmount)
+
+ if (to && amount && gasPrice && gasLimit) {
+ return {
+ isValid: true,
+ errors: {},
+ }
+ }
+
+ const errors = {
+ to: !to ? 'Required' : null,
+ amount: !amount ? 'Required' : null,
+ gasPrice: !gasPrice ? 'Gas Price Required' : null,
+ gasLimit: !gasLimit ? 'Gas Limit Required' : null,
+ }
+
+ return {
+ isValid: false,
+ errors,
+ }
+}
+
+SendTokenScreen.prototype.submit = function () {
+ const {
+ to,
+ amount,
+ gasPrice,
+ gasLimit,
+ } = this.state
+
+ const {
+ identities,
+ selectedAddress,
+ selectedTokenAddress,
+ hideWarning,
+ addToAddressBook,
+ signTokenTx,
+ selectedToken,
+ } = this.props
+
+ const { nickname = ' ' } = identities[to] || {}
+
+ const { isValid, errors } = this.validate()
+
+ if (!isValid) {
+ return this.setState({ errors })
+ }
+
+ hideWarning()
+ addToAddressBook(to, nickname)
+
+ const txParams = {
+ from: selectedAddress,
+ value: '0',
+ gas: gasLimit,
+ gasPrice: gasPrice,
+ }
+
+ const { decimals } = selectedToken || {}
+ const multiplier = Math.pow(10, Number(decimals || 0))
+ const sendAmount = Number(amount * multiplier).toString(16)
+
+ signTokenTx(selectedTokenAddress, to, sendAmount, txParams)
+}
+
+SendTokenScreen.prototype.renderToAddressInput = function () {
+ const {
+ identities,
+ addressBook,
+ } = this.props
+
+ const {
+ to,
+ errors: { to: errorMessage },
+ } = this.state
+
+ return h('div', {
+ className: classnames('send-screen-input-wrapper', {
+ 'send-screen-input-wrapper--error': errorMessage,
+ }),
+ }, [
+ h('div', ['To:']),
+ h('input.large-input.send-screen-input', {
+ name: 'address',
+ list: 'addresses',
+ placeholder: 'Address',
+ value: to,
+ onChange: e => this.setState({
+ to: e.target.value,
+ errors: {},
+ }),
+ }),
+ h('datalist#addresses', [
+ // Corresponds to the addresses owned.
+ Object.entries(identities).map(([key, { address, name }]) => {
+ return h('option', {
+ value: address,
+ label: name,
+ key: address,
+ })
+ }),
+ addressBook.map(({ address, name }) => {
+ return h('option', {
+ value: address,
+ label: name,
+ key: address,
+ })
+ }),
+ ]),
+ h('div.send-screen-input-wrapper__error-message', [ errorMessage ]),
+ ])
+}
+
+SendTokenScreen.prototype.renderAmountInput = function () {
+ const {
+ selectedCurrency,
+ amount,
+ errors: { amount: errorMessage },
+ } = this.state
+
+ const {
+ tokenExchangeRate,
+ selectedToken: {symbol},
+ } = this.props
+
+ return h('div.send-screen-input-wrapper', {
+ className: classnames('send-screen-input-wrapper', {
+ 'send-screen-input-wrapper--error': errorMessage,
+ }),
+ }, [
+ h('div.send-screen-amount-labels', [
+ h('span', ['Amount']),
+ h(CurrencyToggle, {
+ currentCurrency: tokenExchangeRate ? selectedCurrency : 'USD',
+ currencies: tokenExchangeRate ? [ symbol, 'USD' ] : [],
+ onClick: currency => this.setState({ selectedCurrency: currency }),
+ }),
+ ]),
+ h('input.large-input.send-screen-input', {
+ placeholder: `0 ${symbol}`,
+ type: 'number',
+ value: amount,
+ onChange: e => this.setState({
+ amount: e.target.value,
+ errors: {},
+ }),
+ }),
+ h('div.send-screen-input-wrapper__error-message', [ errorMessage ]),
+ ])
+}
+
+SendTokenScreen.prototype.renderGasInput = function () {
+ const {
+ isGasTooltipOpen,
+ gasPrice,
+ gasLimit,
+ selectedCurrency,
+ errors: {
+ gasPrice: gasPriceErrorMessage,
+ gasLimit: gasLimitErrorMessage,
+ },
+ } = this.state
+
+ const {
+ conversionRate,
+ tokenExchangeRate,
+ currentBlockGasLimit,
+ } = this.props
+
+ return h('div.send-screen-input-wrapper', {
+ className: classnames('send-screen-input-wrapper', {
+ 'send-screen-input-wrapper--error': gasPriceErrorMessage || gasLimitErrorMessage,
+ }),
+ }, [
+ isGasTooltipOpen && h(GasTooltip, {
+ className: 'send-tooltip',
+ gasPrice,
+ gasLimit,
+ onClose: () => this.setState({ isGasTooltipOpen: false }),
+ onFeeChange: ({ gasLimit, gasPrice }) => {
+ this.setState({ gasLimit, gasPrice, errors: {} })
+ },
+ }),
+
+ h('div.send-screen-gas-labels', {}, [
+ h('span', [ h('i.fa.fa-bolt'), 'Gas fee:']),
+ h('span', ['What\'s this?']),
+ ]),
+ h('div.large-input.send-screen-gas-input', [
+ h(GasFeeDisplay, {
+ conversionRate,
+ tokenExchangeRate,
+ gasPrice,
+ activeCurrency: selectedCurrency,
+ gas: gasLimit,
+ blockGasLimit: currentBlockGasLimit,
+ }),
+ h(
+ 'div.send-screen-gas-input-customize',
+ { onClick: () => this.setState({ isGasTooltipOpen: !isGasTooltipOpen }) },
+ ['Customize']
+ ),
+ ]),
+ h('div.send-screen-input-wrapper__error-message', [
+ gasPriceErrorMessage || gasLimitErrorMessage,
+ ]),
+ ])
+}
+
+SendTokenScreen.prototype.renderMemoInput = function () {
+ return h('div.send-screen-input-wrapper', [
+ h('div', {}, ['Transaction memo (optional)']),
+ h(
+ 'input.large-input.send-screen-input',
+ { onChange: e => this.setState({ memo: e.target.value }) }
+ ),
+ ])
+}
+
+SendTokenScreen.prototype.renderButtons = function () {
+ const { selectedAddress, backToAccountDetail } = this.props
+
+ return h('div.send-token__button-group', [
+ h('button.send-token__button-next.btn-secondary', {
+ onClick: () => this.submit(),
+ }, ['Next']),
+ h('button.send-token__button-cancel.btn-tertiary', {
+ onClick: () => backToAccountDetail(selectedAddress),
+ }, ['Cancel']),
+ ])
+}
+
+SendTokenScreen.prototype.render = function () {
+ const {
+ selectedTokenAddress,
+ selectedToken,
+ } = this.props
+
+ return h('div.send-token', [
+ h('div.send-token__content', [
+ h(Identicon, {
+ diameter: 75,
+ address: selectedTokenAddress,
+ }),
+ h('div.send-token__title', ['Send Tokens']),
+ h('div.send-token__description', ['Send Tokens to anyone with an Ethereum account']),
+ h('div.send-token__balance-text', ['Your Token Balance is:']),
+ h('div.send-token__token-balance', [
+ h(TokenBalance, { token: selectedToken, balanceOnly: true }),
+ ]),
+ h('div.send-token__token-symbol', [selectedToken.symbol]),
+ this.renderToAddressInput(),
+ this.renderAmountInput(),
+ this.renderGasInput(),
+ this.renderMemoInput(),
+ ]),
+ this.renderButtons(),
+ ])
+}
diff --git a/ui/app/components/send/currency-toggle.js b/ui/app/components/send/currency-toggle.js
new file mode 100644
index 000000000..7aaccd490
--- /dev/null
+++ b/ui/app/components/send/currency-toggle.js
@@ -0,0 +1,44 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const classnames = require('classnames')
+
+module.exports = CurrencyToggle
+
+inherits(CurrencyToggle, Component)
+function CurrencyToggle () {
+ Component.call(this)
+}
+
+const defaultCurrencies = [ 'ETH', 'USD' ]
+
+CurrencyToggle.prototype.renderToggles = function () {
+ const { onClick, activeCurrency } = this.props
+ const [currencyA, currencyB] = this.props.currencies || defaultCurrencies
+
+ return [
+ h('span', {
+ className: classnames('currency-toggle__item', {
+ 'currency-toggle__item--selected': currencyA === activeCurrency,
+ }),
+ onClick: () => onClick(currencyA),
+ }, [ currencyA ]),
+ '<>',
+ h('span', {
+ className: classnames('currency-toggle__item', {
+ 'currency-toggle__item--selected': currencyB === activeCurrency,
+ }),
+ onClick: () => onClick(currencyB),
+ }, [ currencyB ]),
+ ]
+}
+
+CurrencyToggle.prototype.render = function () {
+ const currencies = this.props.currencies || defaultCurrencies
+
+ return h('span.currency-toggle', currencies.length
+ ? this.renderToggles()
+ : []
+ )
+}
+
diff --git a/ui/app/components/send/eth-fee-display.js b/ui/app/components/send/eth-fee-display.js
new file mode 100644
index 000000000..8b4cec16c
--- /dev/null
+++ b/ui/app/components/send/eth-fee-display.js
@@ -0,0 +1,37 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const EthBalance = require('../eth-balance')
+const { getTxFeeBn } = require('../../util')
+
+module.exports = EthFeeDisplay
+
+inherits(EthFeeDisplay, Component)
+function EthFeeDisplay () {
+ Component.call(this)
+}
+
+EthFeeDisplay.prototype.render = function () {
+ const {
+ activeCurrency,
+ conversionRate,
+ gas,
+ gasPrice,
+ blockGasLimit,
+ } = this.props
+
+ return h(EthBalance, {
+ value: getTxFeeBn(gas, gasPrice, blockGasLimit),
+ currentCurrency: activeCurrency,
+ conversionRate,
+ showFiat: false,
+ hideTooltip: true,
+ styleOveride: {
+ color: '#5d5d5d',
+ fontSize: '16px',
+ fontFamily: 'DIN OT',
+ lineHeight: '22.4px'
+ }
+ })
+}
+
diff --git a/ui/app/components/send/gas-fee-display.js b/ui/app/components/send/gas-fee-display.js
new file mode 100644
index 000000000..a9a3f3f49
--- /dev/null
+++ b/ui/app/components/send/gas-fee-display.js
@@ -0,0 +1,62 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const USDFeeDisplay = require('./usd-fee-display')
+const EthFeeDisplay = require('./eth-fee-display')
+const { getTxFeeBn, formatBalance, shortenBalance } = require('../../util')
+
+module.exports = GasFeeDisplay
+
+inherits(GasFeeDisplay, Component)
+function GasFeeDisplay () {
+ Component.call(this)
+}
+
+GasFeeDisplay.prototype.getTokenValue = function () {
+ const {
+ tokenExchangeRate,
+ gas,
+ gasPrice,
+ blockGasLimit,
+ } = this.props
+
+ const value = formatBalance(getTxFeeBn(gas, gasPrice, blockGasLimit), 6, true)
+ const [ethNumber] = value.split(' ')
+
+ return shortenBalance(Number(ethNumber) / tokenExchangeRate, 6)
+}
+
+GasFeeDisplay.prototype.render = function () {
+ const {
+ activeCurrency,
+ conversionRate,
+ gas,
+ gasPrice,
+ blockGasLimit,
+ } = this.props
+
+ switch (activeCurrency) {
+ case 'USD':
+ return h(USDFeeDisplay, {
+ activeCurrency,
+ conversionRate,
+ gas,
+ gasPrice,
+ blockGasLimit,
+ })
+ case 'ETH':
+ return h(EthFeeDisplay, {
+ activeCurrency,
+ conversionRate,
+ gas,
+ gasPrice,
+ blockGasLimit,
+ })
+ default:
+ return h('div.token-gas', [
+ h('div.token-gas__amount', this.getTokenValue()),
+ h('div.token-gas__symbol', activeCurrency),
+ ])
+ }
+}
+
diff --git a/ui/app/components/send/gas-tooltip.js b/ui/app/components/send/gas-tooltip.js
new file mode 100644
index 000000000..bef419e48
--- /dev/null
+++ b/ui/app/components/send/gas-tooltip.js
@@ -0,0 +1,100 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const InputNumber = require('../input-number.js')
+
+module.exports = GasTooltip
+
+inherits(GasTooltip, Component)
+function GasTooltip () {
+ Component.call(this)
+ this.state = {
+ gasLimit: 0,
+ gasPrice: 0,
+ }
+
+ this.updateGasPrice = this.updateGasPrice.bind(this)
+ this.updateGasLimit = this.updateGasLimit.bind(this)
+ this.onClose = this.onClose.bind(this)
+}
+
+GasTooltip.prototype.componentWillMount = function () {
+ const { gasPrice = 0, gasLimit = 0} = this.props
+
+ this.setState({
+ gasPrice: parseInt(gasPrice, 16) / 1000000000,
+ gasLimit: parseInt(gasLimit, 16),
+ })
+}
+
+GasTooltip.prototype.updateGasPrice = function (newPrice) {
+ const { onFeeChange } = this.props
+ const { gasLimit } = this.state
+
+ this.setState({ gasPrice: newPrice })
+ onFeeChange({
+ gasLimit: gasLimit.toString(16),
+ gasPrice: (newPrice * 1000000000).toString(16),
+ })
+}
+
+GasTooltip.prototype.updateGasLimit = function (newLimit) {
+ const { onFeeChange } = this.props
+ const { gasPrice } = this.state
+
+ this.setState({ gasLimit: newLimit })
+ onFeeChange({
+ gasLimit: newLimit.toString(16),
+ gasPrice: (gasPrice * 1000000000).toString(16),
+ })
+}
+
+GasTooltip.prototype.onClose = function (e) {
+ e.stopPropagation()
+ this.props.onClose()
+}
+
+GasTooltip.prototype.render = function () {
+ const { gasPrice, gasLimit } = this.state
+
+ return h('div.gas-tooltip', {}, [
+ h('div.gas-tooltip-close-area', {
+ onClick: this.onClose,
+ }),
+ h('div.customize-gas-tooltip-container', {}, [
+ h('div.customize-gas-tooltip', {}, [
+ h('div.gas-tooltip-header.gas-tooltip-label', {}, ['Customize Gas']),
+ h('div.gas-tooltip-input-label', {}, [
+ h('span.gas-tooltip-label', {}, ['Gas Price']),
+ h('i.fa.fa-info-circle'),
+ ]),
+ h(InputNumber, {
+ unitLabel: 'GWEI',
+ step: 1,
+ min: 0,
+ placeholder: '0',
+ initValue: gasPrice,
+ onChange: (newPrice) => this.updateGasPrice(newPrice),
+ }),
+ h('div.gas-tooltip-input-label', {
+ style: {
+ 'marginTop': '81px',
+ },
+ }, [
+ h('span.gas-tooltip-label', {}, ['Gas Limit']),
+ h('i.fa.fa-info-circle'),
+ ]),
+ h(InputNumber, {
+ unitLabel: 'UNITS',
+ step: 1,
+ min: 0,
+ placeholder: '0',
+ initValue: gasLimit,
+ onChange: (newLimit) => this.updateGasLimit(newLimit),
+ }),
+ ]),
+ h('div.gas-tooltip-arrow', {}),
+ ]),
+ ])
+}
+
diff --git a/ui/app/components/send/usd-fee-display.js b/ui/app/components/send/usd-fee-display.js
new file mode 100644
index 000000000..6ee38f1b5
--- /dev/null
+++ b/ui/app/components/send/usd-fee-display.js
@@ -0,0 +1,35 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const FiatValue = require('../fiat-value')
+const { getTxFeeBn } = require('../../util')
+
+module.exports = USDFeeDisplay
+
+inherits(USDFeeDisplay, Component)
+function USDFeeDisplay () {
+ Component.call(this)
+}
+
+USDFeeDisplay.prototype.render = function () {
+ const {
+ activeCurrency,
+ conversionRate,
+ gas,
+ gasPrice,
+ blockGasLimit,
+ } = this.props
+
+ return h(FiatValue, {
+ value: getTxFeeBn(gas, gasPrice, blockGasLimit),
+ conversionRate,
+ currentCurrency: activeCurrency,
+ style: {
+ color: '#5d5d5d',
+ fontSize: '16px',
+ fontFamily: 'DIN OT',
+ lineHeight: '22.4px'
+ }
+ })
+}
+
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 901a4a956..96a86d3b1 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -131,8 +131,8 @@ ShapeshiftForm.prototype.renderMain = function () {
},
}, [
this.props.warning
- ? this.props.warning
- && h('span.error.flex-center', {
+ ? this.props.warning &&
+ h('span.error.flex-center', {
style: {
textAlign: 'center',
width: '229px',
diff --git a/ui/app/components/token-balance.js b/ui/app/components/token-balance.js
new file mode 100644
index 000000000..3a923eb9d
--- /dev/null
+++ b/ui/app/components/token-balance.js
@@ -0,0 +1,108 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const TokenTracker = require('eth-token-tracker')
+const connect = require('react-redux').connect
+const selectors = require('../selectors')
+
+function mapStateToProps (state) {
+ return {
+ userAddress: selectors.getSelectedAddress(state),
+ }
+}
+
+module.exports = connect(mapStateToProps)(TokenBalance)
+
+
+inherits(TokenBalance, Component)
+function TokenBalance () {
+ this.state = {
+ balance: '',
+ isLoading: true,
+ error: null,
+ }
+ Component.call(this)
+}
+
+TokenBalance.prototype.render = function () {
+ const state = this.state
+ const { balance, isLoading } = state
+
+ return isLoading
+ ? h('span', '')
+ : h('span', balance)
+}
+
+TokenBalance.prototype.componentDidMount = function () {
+ this.createFreshTokenTracker()
+}
+
+TokenBalance.prototype.createFreshTokenTracker = function () {
+ if (this.tracker) {
+ // Clean up old trackers when refreshing:
+ this.tracker.stop()
+ this.tracker.removeListener('update', this.balanceUpdater)
+ this.tracker.removeListener('error', this.showError)
+ }
+
+ if (!global.ethereumProvider) return
+ const { userAddress, token } = this.props
+
+ this.tracker = new TokenTracker({
+ userAddress,
+ provider: global.ethereumProvider,
+ tokens: [token],
+ pollingInterval: 8000,
+ })
+
+
+ // Set up listener instances for cleaning up
+ this.balanceUpdater = this.updateBalance.bind(this)
+ this.showError = error => {
+ this.setState({ error, isLoading: false })
+ }
+ this.tracker.on('update', this.balanceUpdater)
+ this.tracker.on('error', this.showError)
+
+ this.tracker.updateBalances()
+ .then(() => {
+ this.updateBalance(this.tracker.serialize())
+ })
+ .catch((reason) => {
+ log.error(`Problem updating balances`, reason)
+ this.setState({ isLoading: false })
+ })
+}
+
+TokenBalance.prototype.componentDidUpdate = function (nextProps) {
+ const {
+ userAddress: oldAddress,
+ token: { address: oldTokenAddress },
+ } = this.props
+ const {
+ userAddress: newAddress,
+ token: { address: newTokenAddress },
+ } = nextProps
+
+ if ((!oldAddress || !newAddress) && (!oldTokenAddress || !newTokenAddress)) return
+ if ((oldAddress === newAddress) && (oldTokenAddress === newTokenAddress)) return
+
+ this.setState({ isLoading: true })
+ this.createFreshTokenTracker()
+}
+
+TokenBalance.prototype.updateBalance = function (tokens = []) {
+ const [{ string, symbol }] = tokens
+ const { balanceOnly } = this.props
+
+ this.setState({
+ balance: balanceOnly ? string : `${string} ${symbol}`,
+ isLoading: false,
+ })
+}
+
+TokenBalance.prototype.componentWillUnmount = function () {
+ if (!this.tracker) return
+ this.tracker.stop()
+}
+
diff --git a/ui/app/components/token-cell.js b/ui/app/components/token-cell.js
index 19d7139bb..dc1c7f46f 100644
--- a/ui/app/components/token-cell.js
+++ b/ui/app/components/token-cell.js
@@ -1,35 +1,101 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
+const connect = require('react-redux').connect
const Identicon = require('./identicon')
const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
+const selectors = require('../selectors')
+const actions = require('../actions')
+const { conversionUtil } = require('../conversion-util')
-module.exports = TokenCell
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ selectedTokenAddress: state.metamask.selectedTokenAddress,
+ userAddress: selectors.getSelectedAddress(state),
+ tokenExchangeRates: state.metamask.tokenExchangeRates,
+ ethToUSDRate: state.metamask.conversionRate,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setSelectedToken: address => dispatch(actions.setSelectedToken(address)),
+ updateTokenExchangeRate: token => dispatch(actions.updateTokenExchangeRate(token)),
+ }
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenCell)
inherits(TokenCell, Component)
function TokenCell () {
Component.call(this)
}
+TokenCell.prototype.componentWillMount = function () {
+ const {
+ updateTokenExchangeRate,
+ symbol,
+ } = this.props
+
+ updateTokenExchangeRate(symbol)
+}
+
TokenCell.prototype.render = function () {
const props = this.props
- const { address, symbol, string, network, userAddress } = props
+ const {
+ address,
+ symbol,
+ string,
+ network,
+ setSelectedToken,
+ selectedTokenAddress,
+ tokenExchangeRates,
+ ethToUSDRate,
+ // userAddress,
+ } = props
+
+ const pair = `${symbol.toLowerCase()}_eth`;
+
+ let currentTokenToEthRate;
+ let currentTokenInUSD;
+ let formattedUSD = ''
+
+ if (tokenExchangeRates[pair]) {
+ currentTokenToEthRate = tokenExchangeRates[pair].rate;
+ currentTokenInUSD = conversionUtil(string, {
+ fromNumericBase: 'dec',
+ fromCurrency: symbol,
+ toCurrency: 'USD',
+ numberOfDecimals: 2,
+ conversionRate: currentTokenToEthRate,
+ ethToUSDRate,
+ })
+ formattedUSD = `$${currentTokenInUSD} USD`;
+ }
return (
- h('li.token-cell', {
- style: { cursor: network === '1' ? 'pointer' : 'default' },
- onClick: this.view.bind(this, address, userAddress, network),
+ h('div.token-list-item', {
+ className: `token-list-item ${selectedTokenAddress === address ? 'token-list-item--active' : ''}`,
+ // style: { cursor: network === '1' ? 'pointer' : 'default' },
+ // onClick: this.view.bind(this, address, userAddress, network),
+ onClick: () => setSelectedToken(address),
}, [
h(Identicon, {
- diameter: 50,
+ className: 'token-list-item__identicon',
+ diameter: 45,
address,
network,
}),
- h('h3', `${string || 0} ${symbol}`),
+ h('h.token-list-item__balance-wrapper', null, [
+ h('h3.token-list-item__token-balance', `${string || 0} ${symbol}`),
- h('span', { style: { flex: '1 0 auto' } }),
+ h('div.token-list-item__fiat-amount', {
+ style: {},
+ }, formattedUSD),
+ ]),
/*
h('button', {
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 998ec901d..0efa89c63 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -3,8 +3,30 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js')
+const normalizeAddress = require('eth-sig-util').normalize
+const connect = require('react-redux').connect
+const selectors = require('../selectors')
+
+function mapStateToProps (state) {
+ return {
+ network: state.metamask.network,
+ tokens: state.metamask.tokens,
+ userAddress: selectors.getSelectedAddress(state),
+ }
+}
+
+const defaultTokens = []
+const contracts = require('eth-contract-metadata')
+for (const address in contracts) {
+ const contract = contracts[address]
+ if (contract.erc20) {
+ contract.address = address
+ defaultTokens.push(contract)
+ }
+}
+
+module.exports = connect(mapStateToProps)(TokenList)
-module.exports = TokenList
inherits(TokenList, Component)
function TokenList () {
@@ -19,10 +41,9 @@ function TokenList () {
TokenList.prototype.render = function () {
const state = this.state
const { tokens, isLoading, error } = state
- const { userAddress, network } = this.props
if (isLoading) {
- return this.message('Loading')
+ return this.message('Loading Tokens...')
}
if (error) {
@@ -47,87 +68,8 @@ TokenList.prototype.render = function () {
])
}
- const tokenViews = tokens.map((tokenData) => {
- tokenData.network = network
- tokenData.userAddress = userAddress
- return h(TokenCell, tokenData)
- })
-
- return h('.full-flex-height', [
- this.renderTokenStatusBar(),
+ return h('div', tokens.map((tokenData) => h(TokenCell, tokenData)))
- h('ol.full-flex-height.flex-column', {
- style: {
- overflowY: 'auto',
- display: 'flex',
- flexDirection: 'column',
- },
- }, [
- h('style', `
-
- li.token-cell {
- display: flex;
- flex-direction: row;
- align-items: center;
- padding: 10px;
- min-height: 50px;
- }
-
- li.token-cell > h3 {
- margin-left: 12px;
- }
-
- li.token-cell:hover {
- background: white;
- cursor: pointer;
- }
-
- `),
- ...tokenViews,
- h('.flex-grow'),
- ]),
- ])
-}
-
-TokenList.prototype.renderTokenStatusBar = function () {
- const { tokens } = this.state
-
- let msg
- if (tokens.length === 1) {
- msg = `You own 1 token`
- } else if (tokens.length > 1) {
- msg = `You own ${tokens.length} tokens`
- } else {
- msg = `No tokens found`
- }
-
- return h('div', {
- style: {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- minHeight: '70px',
- padding: '10px',
- },
- }, [
- h('span', msg),
- h('button', {
- key: 'reveal-account-bar',
- onClick: (event) => {
- event.preventDefault()
- this.props.addToken()
- },
- style: {
- display: 'flex',
- height: '40px',
- padding: '10px',
- justifyContent: 'center',
- alignItems: 'center',
- },
- }, [
- 'ADD TOKEN',
- ]),
- ])
}
TokenList.prototype.message = function (body) {
@@ -156,6 +98,7 @@ TokenList.prototype.createFreshTokenTracker = function () {
if (!global.ethereumProvider) return
const { userAddress } = this.props
+
this.tracker = new TokenTracker({
userAddress,
provider: global.ethereumProvider,
@@ -182,15 +125,22 @@ TokenList.prototype.createFreshTokenTracker = function () {
})
}
-TokenList.prototype.componentWillUpdate = function (nextProps) {
- if (nextProps.network === 'loading') return
- const oldNet = this.props.network
- const newNet = nextProps.network
-
- if (oldNet && newNet && newNet !== oldNet) {
- this.setState({ isLoading: true })
- this.createFreshTokenTracker()
- }
+TokenList.prototype.componentDidUpdate = function (nextProps) {
+ const {
+ network: oldNet,
+ userAddress: oldAddress,
+ } = this.props
+ const {
+ network: newNet,
+ userAddress: newAddress,
+ } = nextProps
+
+ if (newNet === 'loading') return
+ if (!oldNet || !newNet || !oldAddress || !newAddress) return
+ if (oldAddress === newAddress && oldNet === newNet) return
+
+ this.setState({ isLoading: true })
+ this.createFreshTokenTracker()
}
TokenList.prototype.updateBalances = function (tokens) {
@@ -205,3 +155,15 @@ TokenList.prototype.componentWillUnmount = function () {
this.tracker.stop()
}
+function uniqueMergeTokens (tokensA, tokensB = []) {
+ const uniqueAddresses = []
+ const result = []
+ tokensA.concat(tokensB).forEach((token) => {
+ const normal = normalizeAddress(token.address)
+ if (!uniqueAddresses.includes(normal)) {
+ uniqueAddresses.push(normal)
+ result.push(token)
+ }
+ })
+ return result
+}
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index 0e5c0b5a3..a40066dc7 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -147,7 +147,7 @@ function failIfFailed (transaction) {
}
if (transaction.err || transaction.warning) {
const { err, warning = {} } = transaction
- const errFirst = !!(( err && warning ) || err)
+ const errFirst = !!((err && warning) || err)
const message = errFirst ? err.message : warning.message
errFirst ? err.message : warning.message
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
new file mode 100644
index 000000000..8422c02b9
--- /dev/null
+++ b/ui/app/components/tx-list-item.js
@@ -0,0 +1,187 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const connect = require('react-redux').connect
+const inherits = require('util').inherits
+const classnames = require('classnames')
+const abi = require('human-standard-token-abi')
+const abiDecoder = require('abi-decoder')
+abiDecoder.addABI(abi)
+const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
+const Identicon = require('./identicon')
+
+const { conversionUtil } = require('../conversion-util')
+
+module.exports = connect(mapStateToProps)(TxListItem)
+
+function mapStateToProps (state) {
+ return {
+ tokens: state.metamask.tokens,
+ }
+}
+
+inherits(TxListItem, Component)
+function TxListItem () {
+ Component.call(this)
+}
+
+TxListItem.prototype.getAddressText = function () {
+ const {
+ address,
+ txParams = {},
+ } = this.props
+
+ const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
+ const { name: txDataName, params = [] } = decodedData || {}
+ const { value } = params[0] || {}
+
+ switch (txDataName) {
+ case 'transfer':
+ return `${value.slice(0, 10)}...${value.slice(-4)}`
+ default:
+ return address
+ ? `${address.slice(0, 10)}...${address.slice(-4)}`
+ : 'Contract Published'
+ }
+}
+
+TxListItem.prototype.getSendEtherTotal = function () {
+ const {
+ transactionAmount,
+ conversionRate,
+ address,
+ } = this.props
+
+ if (!address) {
+ return {}
+ }
+
+ const totalInUSD = conversionUtil(transactionAmount, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency: 'USD',
+ fromDenomination: 'WEI',
+ numberOfDecimals: 2,
+ conversionRate,
+ })
+ const totalInETH = conversionUtil(transactionAmount, {
+ fromNumericBase: 'hex',
+ toNumericBase: 'dec',
+ fromCurrency: 'ETH',
+ toCurrency: 'ETH',
+ fromDenomination: 'WEI',
+ conversionRate,
+ numberOfDecimals: 6,
+ })
+
+ return {
+ total: `${totalInETH} ETH`,
+ fiatTotal: `$${totalInUSD} USD`,
+ }
+}
+
+TxListItem.prototype.getSendTokenTotal = function () {
+ const {
+ txParams = {},
+ tokens,
+ } = this.props
+
+ const toAddress = txParams.to
+ const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
+ const { params = [] } = decodedData || {}
+ const { value } = params[1] || {}
+ const { decimals, symbol } = tokens.filter(({ address }) => address === toAddress)[0] || {}
+
+ const multiplier = Math.pow(10, Number(decimals || 0))
+ const total = Number(value / multiplier)
+
+ return {
+ total: `${total} ${symbol}`,
+ }
+}
+
+TxListItem.prototype.render = function () {
+ const {
+ transactionStatus,
+ onClick,
+ transActionId,
+ dateString,
+ address,
+ className,
+ txParams = {},
+ } = this.props
+
+ const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data)
+ const { name: txDataName } = decodedData || {}
+
+ const { total, fiatTotal } = txDataName === 'transfer'
+ ? this.getSendTokenTotal()
+ : this.getSendEtherTotal()
+
+ return h(`div${className || ''}`, {
+ key: transActionId,
+ onClick: () => onClick && onClick(transActionId),
+ }, [
+ h(`div.flex-column.tx-list-item-wrapper`, {}, [
+
+ h('div.tx-list-date-wrapper', {
+ style: {},
+ }, [
+ h('span.tx-list-date', {}, [
+ dateString,
+ ]),
+ ]),
+
+ h('div.flex-row.tx-list-content-wrapper', {
+ style: {},
+ }, [
+
+ h('div.tx-list-identicon-wrapper', {
+ style: {},
+ }, [
+ h(Identicon, {
+ address,
+ diameter: 28,
+ }),
+ ]),
+
+ h('div.tx-list-account-and-status-wrapper', {}, [
+ h('div.tx-list-account-wrapper', {
+ style: {},
+ }, [
+ h('span.tx-list-account', {}, [
+ this.getAddressText(address),
+ ]),
+ ]),
+
+ h('div.tx-list-status-wrapper', {
+ style: {},
+ }, [
+ h('span', {
+ className: classnames('tx-list-status', {
+ 'tx-list-status--rejected': transactionStatus === 'rejected',
+ 'tx-list-status--failed': transactionStatus === 'failed',
+ }),
+ },
+ transactionStatus,
+ ),
+ ]),
+ ]),
+
+ h('div.flex-column.tx-list-details-wrapper', {
+ style: {},
+ }, [
+
+ h('span', {
+ className: classnames('tx-list-value', {
+ 'tx-list-value--confirmed': transactionStatus === 'confirmed',
+ }),
+ }, total),
+
+ h('span.tx-list-fiat-value', fiatTotal),
+
+ ]),
+ ]),
+ ]) // holding on icon from design
+ ])
+}
diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js
new file mode 100644
index 000000000..7a147e942
--- /dev/null
+++ b/ui/app/components/tx-list.js
@@ -0,0 +1,117 @@
+const Component = require('react').Component
+const connect = require('react-redux').connect
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
+const selectors = require('../selectors')
+const TxListItem = require('./tx-list-item')
+const { formatBalance, formatDate } = require('../util')
+const { showConfTxPage } = require('../actions')
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(TxList)
+
+function mapStateToProps (state) {
+ return {
+ txsToRender: selectors.transactionsSelector(state),
+ conversionRate: selectors.conversionRateSelector(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showConfTxPage: ({ id }) => dispatch(showConfTxPage({ id }))
+ }
+}
+
+inherits(TxList, Component)
+function TxList () {
+ Component.call(this)
+}
+
+TxList.prototype.render = function () {
+
+ const { txsToRender, showConfTxPage } = this.props
+
+ return h('div.flex-column.tx-list-container', {}, [
+
+ h('div.flex-row.tx-list-header-wrapper', [
+ h('div.flex-row.tx-list-header', [
+ h('div', 'transactions'),
+ ]),
+ ]),
+
+ this.renderTransaction(),
+
+ ])
+}
+
+TxList.prototype.renderTransaction = function () {
+ const { txsToRender, conversionRate } = this.props
+
+ return txsToRender.length
+ ? txsToRender.map((transaction, i) => this.renderTransactionListItem(transaction, conversionRate))
+ : [h('div.tx-list-item.tx-list-item--empty', [ 'No Transactions' ])]
+}
+
+// TODO: Consider moving TxListItem into a separate component
+TxList.prototype.renderTransactionListItem = function (transaction, conversionRate) {
+ const props = {
+ dateString: formatDate(transaction.time),
+ address: transaction.txParams.to,
+ transactionStatus: transaction.status,
+ transactionAmount: transaction.txParams.value,
+ transActionId: transaction.id,
+ transactionHash: transaction.hash,
+ transactionNetworkId: transaction.metamaskNetworkId,
+ }
+
+ const {
+ address,
+ transactionStatus,
+ transactionAmount,
+ dateString,
+ transActionId,
+ transactionHash,
+ transactionNetworkId,
+ } = props
+ const { showConfTxPage } = this.props
+
+ const opts = {
+ key: transActionId,
+ txParams: transaction.txParams,
+ transactionStatus,
+ transActionId,
+ dateString,
+ address,
+ transactionAmount,
+ transactionHash,
+ className: '.tx-list-item.tx-list-clickable',
+ conversionRate,
+ }
+
+ if (transactionStatus === 'unapproved') {
+ opts.onClick = () => showConfTxPage({id: transActionId})
+ opts.className += '.tx-list-pending-item-container'
+ opts.transactionStatus = 'Not Started'
+ } else if (transactionHash) {
+ opts.onClick = () => this.view(transactionHash, transactionNetworkId)
+ }
+
+ return h(TxListItem, opts)
+}
+
+TxList.prototype.view = function (txHash, network) {
+ const url = etherscanLinkFor(txHash, network)
+ if (url) {
+ navigateTo(url)
+ }
+}
+
+function navigateTo (url) {
+ global.platform.openWindow({ url })
+}
+
+function etherscanLinkFor (txHash, network) {
+ const prefix = prefixForNetwork(network)
+ return `https://${prefix}etherscan.io/tx/${txHash}`
+}
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
new file mode 100644
index 000000000..59f55d485
--- /dev/null
+++ b/ui/app/components/tx-view.js
@@ -0,0 +1,148 @@
+const Component = require('react').Component
+const connect = require('react-redux').connect
+const h = require('react-hyperscript')
+const ethUtil = require('ethereumjs-util')
+const inherits = require('util').inherits
+const actions = require('../actions')
+const selectors = require('../selectors')
+
+const BalanceComponent = require('./balance-component')
+const TxList = require('./tx-list')
+const Identicon = require('./identicon')
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(TxView)
+
+function mapStateToProps (state) {
+ const sidebarOpen = state.appState.sidebarOpen
+
+ const identities = state.metamask.identities
+ const accounts = state.metamask.accounts
+ const network = state.metamask.network
+ const selectedTokenAddress = state.metamask.selectedTokenAddress
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
+ const checksumAddress = selectedAddress && ethUtil.toChecksumAddress(selectedAddress)
+ const identity = identities[selectedAddress]
+
+ return {
+ sidebarOpen,
+ selectedAddress,
+ checksumAddress,
+ selectedTokenAddress,
+ selectedToken: selectors.getSelectedToken(state),
+ identity,
+ network,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showSidebar: () => { dispatch(actions.showSidebar()) },
+ hideSidebar: () => { dispatch(actions.hideSidebar()) },
+ showModal: (payload) => { dispatch(actions.showModal(payload)) },
+ showSendPage: () => { dispatch(actions.showSendPage()) },
+ showSendTokenPage: () => { dispatch(actions.showSendTokenPage()) },
+ }
+}
+
+inherits(TxView, Component)
+function TxView () {
+ Component.call(this)
+}
+
+TxView.prototype.renderHeroBalance = function () {
+ const { selectedToken } = this.props
+
+ return h('div.hero-balance', {}, [
+
+ h(BalanceComponent, { token: selectedToken }),
+
+ this.renderButtons(),
+ ])
+}
+
+TxView.prototype.renderButtons = function () {
+ const {selectedToken, showModal, showSendPage, showSendTokenPage } = this.props
+
+ return !selectedToken
+ ? (
+ h('div.flex-row.flex-center.hero-balance-buttons', [
+ h('button.btn-clear', {
+ style: {
+ textAlign: 'center',
+ },
+ onClick: () => showModal({
+ name: 'BUY',
+ }),
+ }, 'BUY'),
+
+ h('button.btn-clear', {
+ style: {
+ textAlign: 'center',
+ marginLeft: '0.8em',
+ },
+ onClick: showSendPage,
+ }, 'SEND'),
+ ])
+ )
+ : (
+ h('div.flex-row.flex-center.hero-balance-buttons', [
+ h('button.btn-clear', {
+ style: {
+ textAlign: 'center',
+ marginLeft: '0.8em',
+ },
+ onClick: showSendTokenPage,
+ }, 'SEND'),
+ ])
+ )
+}
+
+TxView.prototype.render = function () {
+ const { selectedAddress, identity, network } = this.props
+
+ return h('div.tx-view.flex-column', {
+ style: {},
+ }, [
+
+ h('div.flex-row.phone-visible', {
+ style: {
+ 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',
+ },
+ }, []),
+
+ h('.identicon-wrapper.select-none', {
+ style: {
+ marginLeft: '0.9em',
+ },
+ }, [
+ h(Identicon, {
+ diameter: 24,
+ address: selectedAddress,
+ network,
+ }),
+ ]),
+
+ h('span.account-name', {
+ style: {},
+ }, [
+ identity.name,
+ ]),
+
+ ]),
+
+ this.renderHeroBalance(),
+
+ h(TxList),
+
+ ])
+}
diff --git a/ui/app/components/wallet-content-display.js b/ui/app/components/wallet-content-display.js
new file mode 100644
index 000000000..bfa061be4
--- /dev/null
+++ b/ui/app/components/wallet-content-display.js
@@ -0,0 +1,56 @@
+const Component = require('react').Component
+const h = require('react-hyperscript')
+const inherits = require('util').inherits
+
+module.exports = WalletContentDisplay
+
+inherits(WalletContentDisplay, Component)
+function WalletContentDisplay () {
+ Component.call(this)
+}
+
+WalletContentDisplay.prototype.render = function () {
+ const { title, amount, fiatValue, active, style } = this.props
+
+ // TODO: Separate component: wallet-content-account
+ return h('div.flex-column', {
+ style: {
+ marginLeft: '1.3em',
+ alignItems: 'flex-start',
+ ...style,
+ },
+ }, [
+
+ h('span', {
+ style: {
+ fontSize: '1.1em',
+ },
+ }, title),
+
+ h('span', {
+ style: {
+ fontSize: '1.8em',
+ margin: '0.4em 0em',
+ },
+ }, amount),
+
+ h('span', {
+ style: {
+ fontSize: '1.3em',
+ },
+ }, fiatValue),
+
+ active && h('div', {
+ style: {
+ position: 'absolute',
+ marginLeft: '-1.3em',
+ height: '6em',
+ width: '0.3em',
+ background: '#D8D8D8', // $alto
+ },
+ }, [
+ ]),
+ ])
+
+}
+
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
new file mode 100644
index 000000000..b306fb7d4
--- /dev/null
+++ b/ui/app/components/wallet-view.js
@@ -0,0 +1,170 @@
+const Component = require('react').Component
+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 actions = require('../actions')
+const BalanceComponent = require('./balance-component')
+const TokenList = require('./token-list')
+const selectors = require('../selectors')
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView)
+
+function mapStateToProps (state) {
+
+ return {
+ network: state.metamask.network,
+ sidebarOpen: state.appState.sidebarOpen,
+ identities: state.metamask.identities,
+ accounts: state.metamask.accounts,
+ tokens: state.metamask.tokens,
+ selectedAddress: selectors.getSelectedAddress(state),
+ selectedIdentity: selectors.getSelectedIdentity(state),
+ selectedAccount: selectors.getSelectedAccount(state),
+ selectedTokenAddress: state.metamask.selectedTokenAddress,
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ showSendPage: () => { dispatch(actions.showSendPage()) },
+ hideSidebar: () => { dispatch(actions.hideSidebar()) },
+ unsetSelectedToken: () => dispatch(actions.setSelectedToken()),
+ }
+}
+
+inherits(WalletView, Component)
+function WalletView () {
+ Component.call(this)
+}
+
+WalletView.prototype.renderWalletBalance = function () {
+ const { selectedTokenAddress, selectedAccount, unsetSelectedToken } = this.props
+ const selectedClass = selectedTokenAddress
+ ? ''
+ : 'wallet-balance-wrapper--active'
+ const className = `flex-column wallet-balance-wrapper ${selectedClass}`
+
+ return h('div', { className }, [
+ h('div.wallet-balance',
+ {
+ onClick: unsetSelectedToken,
+ },
+ [
+ h(BalanceComponent, {
+ balanceValue: selectedAccount.balance,
+ style: {},
+ }),
+ ]
+ ),
+ ])
+}
+
+WalletView.prototype.render = function () {
+ const {
+ network, responsiveDisplayClassname, identities,
+ selectedAddress, accounts,
+ selectedIdentity,
+ } = this.props
+ // temporary logs + fake extra wallets
+ // console.log('walletview, selectedAccount:', selectedAccount)
+
+ return h('div.wallet-view.flex-column' + (responsiveDisplayClassname || ''), {
+ style: {},
+ }, [
+
+ // TODO: Separate component: wallet account details
+ h('div.flex-column.wallet-view-account-details', {
+ style: {},
+ }, [
+
+ 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.flex-column.flex-center', {
+ }, [
+ 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,
+ enableAccountsSelector: true,
+ }, []),
+ ]),
+
+ h(Identicon, {
+ diameter: 54,
+ address: selectedAddress,
+ }),
+
+ h('span.account-name', {
+ style: {},
+ }, [
+ selectedIdentity.name,
+ ]),
+
+ ]),
+ ]),
+
+ // 'Wallet' - Title
+ // Not visible on mobile
+ h('div.flex-column.wallet-view-title-wrapper', {}, [
+ h('span.wallet-view-title', {}, [
+ 'Wallet',
+ ]),
+ ]),
+
+ this.renderWalletBalance(),
+
+ h(TokenList),
+
+ ])
+}
+
+// TODO: Extra wallets, for dev testing. Remove when PRing to master.
+// const extraWallet = h('div.flex-column.wallet-balance-wrapper', {}, [
+// h('div.wallet-balance', {}, [
+// h(BalanceComponent, {
+// balanceValue: selectedAccount.balance,
+// style: {},
+// }),
+// ]),
+// ])
diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js
index 1ee4166f7..7062eee6b 100644
--- a/ui/app/conf-tx.js
+++ b/ui/app/conf-tx.js
@@ -3,15 +3,22 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
-const NetworkIndicator = require('./components/network')
const txHelper = require('../lib/tx-helper')
-const isPopupOrNotification = require('../../app/scripts/lib/is-popup-or-notification')
const PendingTx = require('./components/pending-tx')
const PendingMsg = require('./components/pending-msg')
const PendingPersonalMsg = require('./components/pending-personal-msg')
const Loading = require('./components/loading')
+const contentDivider = h('div', {
+ style: {
+ marginLeft: '16px',
+ marginRight: '16px',
+ height:'1px',
+ background:'#E7E7E7',
+ },
+})
+
module.exports = connect(mapStateToProps)(ConfirmTxScreen)
function mapStateToProps (state) {
@@ -39,83 +46,37 @@ function ConfirmTxScreen () {
ConfirmTxScreen.prototype.render = function () {
const props = this.props
- const { network, provider, unapprovedTxs, currentCurrency,
- unapprovedMsgs, unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props
+ const { network, unapprovedTxs, currentCurrency, unapprovedMsgs,
+ unapprovedPersonalMsgs, conversionRate, blockGasLimit } = props
var unconfTxList = txHelper(unapprovedTxs, unapprovedMsgs, unapprovedPersonalMsgs, network)
var txData = unconfTxList[props.index] || {}
var txParams = txData.params || {}
- var isNotification = isPopupOrNotification() === 'notification'
-
log.info(`rendering a combined ${unconfTxList.length} unconf msg & txs`)
if (unconfTxList.length === 0) return h(Loading, { isLoading: true })
- const unconfTxListLength = unconfTxList.length
-
- return (
-
- h('.flex-column.flex-grow', [
-
- // subtitle and nav
- h('.section-title.flex-row.flex-center', [
- !isNotification ? h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- onClick: this.goHome.bind(this),
- }) : null,
- h('h2.page-subtitle', 'Confirm Transaction'),
- isNotification ? h(NetworkIndicator, {
- network: network,
- provider: provider,
- }) : null,
- ]),
-
- h('h3', {
- style: {
- alignSelf: 'center',
- display: unconfTxList.length > 1 ? 'block' : 'none',
- },
- }, [
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer', {
- style: {
- display: props.index === 0 ? 'none' : 'inline-block',
- },
- onClick: () => props.dispatch(actions.previousTx()),
- }),
- ` ${props.index + 1} of ${unconfTxList.length} `,
- h('i.fa.fa-arrow-right.fa-lg.cursor-pointer', {
- style: {
- display: props.index + 1 === unconfTxList.length ? 'none' : 'inline-block',
- },
- onClick: () => props.dispatch(actions.nextTx()),
- }),
- ]),
-
- warningIfExists(props.warning),
-
- currentTxView({
- // Properties
- txData: txData,
- key: txData.id,
- selectedAddress: props.selectedAddress,
- accounts: props.accounts,
- identities: props.identities,
- conversionRate,
- currentCurrency,
- blockGasLimit,
- unconfTxListLength,
- // Actions
- buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
- sendTransaction: this.sendTransaction.bind(this),
- cancelTransaction: this.cancelTransaction.bind(this, txData),
- cancelAllTransactions: this.cancelAllTransactions.bind(this, unconfTxList),
- signMessage: this.signMessage.bind(this, txData),
- signPersonalMessage: this.signPersonalMessage.bind(this, txData),
- cancelMessage: this.cancelMessage.bind(this, txData),
- cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
- }),
- ])
- )
+ return currentTxView({
+ // Properties
+ txData: txData,
+ key: txData.id,
+ selectedAddress: props.selectedAddress,
+ accounts: props.accounts,
+ identities: props.identities,
+ conversionRate,
+ currentCurrency,
+ blockGasLimit,
+ // Actions
+ buyEth: this.buyEth.bind(this, txParams.from || props.selectedAddress),
+ sendTransaction: this.sendTransaction.bind(this),
+ cancelTransaction: this.cancelTransaction.bind(this, txData),
+ signMessage: this.signMessage.bind(this, txData),
+ signPersonalMessage: this.signPersonalMessage.bind(this, txData),
+ cancelMessage: this.cancelMessage.bind(this, txData),
+ cancelPersonalMessage: this.cancelPersonalMessage.bind(this, txData),
+ })
+
}
function currentTxView (opts) {
@@ -137,6 +98,7 @@ function currentTxView (opts) {
return h(PendingPersonalMsg, opts)
}
}
+ return h(Loading, { isLoading: true })
}
ConfirmTxScreen.prototype.buyEth = function (address, event) {
@@ -200,14 +162,14 @@ ConfirmTxScreen.prototype.goHome = function (event) {
this.props.dispatch(actions.goHome())
}
-function warningIfExists (warning) {
- if (warning &&
- // Do not display user rejections on this screen:
- warning.indexOf('User denied transaction signature') === -1) {
- return h('.error', {
- style: {
- margin: 'auto',
- },
- }, warning)
- }
-}
+// function warningIfExists (warning) {
+// if (warning &&
+// // Do not display user rejections on this screen:
+// warning.indexOf('User denied transaction signature') === -1) {
+// return h('.error', {
+// style: {
+// margin: 'auto',
+// },
+// }, warning)
+// }
+// }
diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js
new file mode 100644
index 000000000..b440aea7f
--- /dev/null
+++ b/ui/app/conversion-util.js
@@ -0,0 +1,119 @@
+/* Currency Conversion Utility
+* This utility function can be used for converting currency related values within metamask.
+* The caller should be able to pass it a value, along with information about the value's
+* numeric base, denomination and currency, and the desired numeric base, denomination and
+* currency. It should return a single value.
+*
+* @param {(number | string | BN)} value The value to convert.
+* @param {Object} [options] Options to specify details of the conversion
+* @param {string} [options.fromCurrency = 'ETH' | 'USD'] The currency of the passed value
+* @param {string} [options.toCurrency = 'ETH' | 'USD'] The desired currency of the result
+* @param {string} [options.fromNumericBase = 'hex' | 'dec' | 'BN'] The numeric basic of the passed value.
+* @param {string} [options.toNumericBase = 'hex' | 'dec' | 'BN'] The desired numeric basic of the result.
+* @param {string} [options.fromDenomination = 'WEI'] The denomination of the passed value
+* @param {number} [options.numberOfDecimals] The desired number of in the result
+* @param {number} [options.conversionRate] The rate to use to make the fromCurrency -> toCurrency conversion
+* @param {number} [options.ethToUSDRate] If present, a second conversion - at ethToUSDRate - happens after conversionRate is applied.
+* @returns {(number | string | BN)}
+*
+* The utility passes value along with the options as a single object to the `converter` function.
+* `converter` uses Ramda.js to apply a composition of conditional setters to the `value` property, depending
+* on the accompanying options. Some of these conditional setters are selected via key-value maps, where
+* the keys are specified in the options parameters and the values are setter functions.
+*/
+
+const BigNumber = require('bignumber.js')
+const R = require('ramda')
+const { stripHexPrefix } = require('ethereumjs-util')
+
+BigNumber.config({
+ ROUNDING_MODE: BigNumber.ROUND_HALF_DOWN,
+})
+
+// Big Number Constants
+const BIG_NUMBER_WEI_MULTIPLIER = new BigNumber('1000000000000000000')
+
+// Individual Setters
+const convert = R.invoker(1, 'times')
+const round = R.invoker(2, 'toFormat')(R.__, BigNumber.ROUND_DOWN)
+
+// Setter Maps
+const toBigNumber = {
+ hex: n => new BigNumber(stripHexPrefix(n), 16),
+ dec: n => new BigNumber(n, 10),
+ BN: n => new BigNumber(n.toString(16), 16),
+}
+const toNormalizedDenomination = {
+ WEI: bigNumber => bigNumber.div(BIG_NUMBER_WEI_MULTIPLIER)
+}
+const toSpecifiedDenomination = {
+ WEI: bigNumber => bigNumber.times(BIG_NUMBER_WEI_MULTIPLIER)
+}
+const baseChange = {
+ hex: n => n.toString(16),
+ dec: n => n.toString(10),
+ BN: n => new BN(n.toString(16)),
+}
+
+// Predicates
+const fromAndToCurrencyPropsNotEqual = R.compose(
+ R.not,
+ R.eqBy(R.__, 'fromCurrency', 'toCurrency'),
+ R.flip(R.prop)
+)
+
+// Lens
+const valuePropertyLense = R.over(R.lensProp('value'))
+
+// conditional 'value' setting wrappers
+const whenPredSetWithPropAndSetter = (pred, prop, setter) => R.when(
+ pred,
+ R.converge(
+ valuePropertyLense,
+ [R.pipe(R.prop(prop), setter), R.identity]
+ )
+)
+const whenPropApplySetterMap = (prop, setterMap) => whenPredSetWithPropAndSetter(
+ R.prop(prop),
+ prop,
+ R.prop(R.__, setterMap)
+)
+
+// Conversion utility function
+const converter = R.pipe(
+ whenPropApplySetterMap('fromNumericBase', toBigNumber),
+ whenPropApplySetterMap('fromDenomination', toNormalizedDenomination),
+ whenPropApplySetterMap('toDenomination', toSpecifiedDenomination),
+ whenPredSetWithPropAndSetter(fromAndToCurrencyPropsNotEqual, 'conversionRate', convert),
+ whenPredSetWithPropAndSetter(R.prop('ethToUSDRate'), 'ethToUSDRate', convert),
+ whenPredSetWithPropAndSetter(R.prop('numberOfDecimals'), 'numberOfDecimals', round),
+ whenPropApplySetterMap('toNumericBase', baseChange),
+ R.view(R.lensProp('value'))
+);
+
+const conversionUtil = (value, {
+ fromCurrency = null,
+ toCurrency = fromCurrency,
+ fromNumericBase,
+ toNumericBase,
+ fromDenomination,
+ toDenomination,
+ numberOfDecimals,
+ conversionRate,
+ ethToUSDRate,
+}) => converter({
+ fromCurrency,
+ toCurrency,
+ fromNumericBase,
+ toNumericBase,
+ fromDenomination,
+ toDenomination,
+ numberOfDecimals,
+ conversionRate,
+ ethToUSDRate,
+ value,
+});
+
+module.exports = {
+ conversionUtil,
+} \ No newline at end of file
diff --git a/ui/app/css/debug.css b/ui/app/css/debug.css
deleted file mode 100644
index 3e125bcd4..000000000
--- a/ui/app/css/debug.css
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
-debug / dev
-*/
-
-#app-content {
- border: 2px solid green;
-}
-
-#design-container {
- position: absolute;
- left: 360px;
- top: -42px;
- width: calc(100vw - 360px);
- height: 100vh;
- overflow: scroll;
-}
-
-#design-container img {
- width: 2000px;
- margin-right: 600px;
-} \ No newline at end of file
diff --git a/ui/app/css/fonts.css b/ui/app/css/fonts.css
deleted file mode 100644
index 3b9f581b9..000000000
--- a/ui/app/css/fonts.css
+++ /dev/null
@@ -1,36 +0,0 @@
-@import url(https://fonts.googleapis.com/css?family=Roboto:300,500);
-@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css);
-
-@font-face {
- font-family: 'Montserrat Regular';
- src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff');
- src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype');
- font-weight: normal;
- font-style: normal;
- font-size: 'small';
-
-}
-
-@font-face {
- font-family: 'Montserrat Bold';
- src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff');
- src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype');
- font-weight: normal;
- font-style: normal;
-}
-
-@font-face {
- font-family: 'Montserrat Light';
- src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff');
- src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype');
- font-weight: normal;
- font-style: normal;
-}
-
-@font-face {
- font-family: 'Montserrat UltraLight';
- src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff');
- src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype');
- font-weight: normal;
- font-style: normal;
-}
diff --git a/ui/app/css/index.scss b/ui/app/css/index.scss
new file mode 100644
index 000000000..01899ccad
--- /dev/null
+++ b/ui/app/css/index.scss
@@ -0,0 +1,13 @@
+/*
+ ITCSS
+
+ http://www.creativebloq.com/web-design/manage-large-css-projects-itcss-101517528
+ https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/
+ */
+@import './itcss/settings/index.scss';
+@import './itcss/tools/index.scss';
+@import './itcss/generic/index.scss';
+@import './itcss/base/index.scss';
+@import './itcss/objects/index.scss';
+@import './itcss/components/index.scss';
+@import './itcss/trumps/index.scss';
diff --git a/ui/app/css/itcss/base/index.scss b/ui/app/css/itcss/base/index.scss
new file mode 100644
index 000000000..baa6ea037
--- /dev/null
+++ b/ui/app/css/itcss/base/index.scss
@@ -0,0 +1 @@
+// Base
diff --git a/ui/app/css/itcss/components/account-dropdown.scss b/ui/app/css/itcss/components/account-dropdown.scss
new file mode 100644
index 000000000..1c4620e40
--- /dev/null
+++ b/ui/app/css/itcss/components/account-dropdown.scss
@@ -0,0 +1,17 @@
+.account-dropdown-name {
+ font-family: 'DIN OT';
+}
+
+.account-dropdown-balance {
+ color: $dusty-gray;
+ line-height: 19px;
+}
+
+.account-dropdown-edit-button {
+ color: $dusty-gray;
+ font-family: "DIN OT";
+
+ &:hover {
+ color: $white;
+ }
+}
diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss
new file mode 100644
index 000000000..0946cdbbb
--- /dev/null
+++ b/ui/app/css/itcss/components/buttons.scss
@@ -0,0 +1,102 @@
+/*
+ Buttons
+ */
+
+.btn-green {
+ background-color: #02c9b1; // TODO: reusable color in colors.css
+}
+
+button.btn-clear {
+ background: $white;
+ border: 1px solid;
+}
+
+// No longer used in flat design, remove when modal buttons done
+// div.wallet-btn {
+// border: 1px solid rgb(91, 93, 103);
+// border-radius: 2px;
+// height: 30px;
+// width: 75px;
+// font-size: 0.8em;
+// text-align: center;
+// line-height: 25px;
+// }
+
+// .btn-red {
+// background: rgba(254, 35, 17, 1);
+// box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36);
+// }
+
+button[disabled],
+input[type="submit"][disabled] {
+ cursor: not-allowed;
+ background: rgba(197, 197, 197, 1);
+ box-shadow: 0 3px 6px rgba(197, 197, 197, .36);
+}
+
+// button.spaced {
+// margin: 2px;
+// }
+
+// button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover {
+// transform: scale(1.1);
+// }
+// button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
+// transform: scale(0.95);
+// }
+
+button.primary {
+ padding: 8px 12px;
+ background: #f7861c;
+ box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
+ color: $white;
+ font-size: 1.1em;
+ font-family: 'Montserrat Regular';
+ text-transform: uppercase;
+}
+
+.btn-light {
+ padding: 8px 12px;
+ // background: #FFFFFF; // $bg-white
+ box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
+ color: #585d67; // TODO: make reusable light button color
+ font-size: 1.1em;
+ font-family: 'Montserrat Regular';
+ text-transform: uppercase;
+ text-align: center;
+ line-height: 20px;
+ border-radius: 2px;
+ border: 1px solid #979797; // #TODO: make reusable light border color
+ opacity: .5;
+}
+
+// TODO: cleanup: not used anywhere
+button.btn-thin {
+ border: 1px solid;
+ border-color: #4d4d4d;
+ color: #4d4d4d;
+ background: rgb(255, 174, 41);
+ border-radius: 4px;
+ min-width: 200px;
+ margin: 12px 0;
+ padding: 6px;
+ font-size: 13px;
+}
+
+.btn-secondary {
+ border: 1px solid #979797;
+ border-radius: 2px;
+ background-color: $white;
+ font-size: 16px;
+ line-height: 24px;
+ padding: 16px 42px;
+}
+
+.btn-tertiary {
+ border: 1px solid transparent;
+ border-radius: 2px;
+ background-color: transparent;
+ font-size: 16px;
+ line-height: 24px;
+ padding: 16px 42px;
+}
diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss
new file mode 100644
index 000000000..e8169ffea
--- /dev/null
+++ b/ui/app/css/itcss/components/confirm.scss
@@ -0,0 +1,257 @@
+.confirm-screen-container {
+ position: absolute;
+ align-items: center;
+
+ @media screen and (max-width: 575px) {
+ margin-top: 35px;
+ width: 100%;
+ }
+
+ @media screen and (min-width: 576px) {
+ margin-top: 6.9vh;
+ }
+}
+
+.confirm-screen-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ z-index: 100;
+ top: 5%;
+ font-family: 'DIN NEXT';
+ background: $white;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .08);
+ // padding: 20px 24px 32px;
+ color: $scorpion;
+ width: 100%;
+
+ @media screen and (min-width: $break-large) {
+ width: 498px;
+ }
+}
+
+.confirm-screen-wrapper > .confirm-screen-total-box {
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.confirm-screen-wrapper > .confirm-memo-wrapper {
+ margin: 0;
+}
+
+.confirm-screen-wrapper > .confirm-screen-header {
+
+ @media screen and (max-width: $break-small) {
+ margin-left: 8px;
+ }
+}
+
+.confirm-screen-header {
+ font-size: 26px;
+ position: relative;
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ width: 100%;
+ padding: 20px 24px 0;
+
+ @media screen and (max-width: $break-small) {
+ font-size: 22px;
+ }
+}
+
+.confirm-screen-title {
+ line-height: 27px;
+
+ @media screen and (max-width: $break-small) {
+ margin-left: 22px;
+ margin-right: 8px;
+ }
+}
+
+.confirm-screen-back-button {
+ background: $white;
+ border: 1px solid $dusty-gray;
+ left: 24px;
+ position: absolute;
+ text-align: center;
+ color: $black;
+ padding: 6px 13px 7px 12px;
+ border-radius: 2px;
+ height: 30px;
+ width: 54px;
+
+ @media screen and (max-width: $break-small) {
+ margin-right: 12px;
+ }
+}
+
+.confirm-screen-account-wrapper {
+ display: flex;
+ flex-direction: column;
+}
+
+.confirm-screen-account-name {
+ margin-top: 12px;
+}
+
+.confirm-screen-account-name,
+.confirm-screen-row-info {
+ font-size: 16px;
+ line-height: 24px;
+ color: $scorpion;
+ text-align: center;
+}
+
+.confirm-screen-account-number {
+ font-size: 10px;
+ line-height: 16px;
+ color: $dusty-gray;
+ text-align: center;
+}
+
+.confirm-screen-identicons {
+ margin-top: 24px;
+
+ i {
+ align-self: start;
+ margin: 42px 14px 0;
+ }
+}
+
+.confirm-screen-sending-to-message {
+ text-align: center;
+ font-size: 16px;
+ margin-top: 30px;
+ font-family: 'DIN NEXT Light';
+}
+
+.confirm-screen-send-amount {
+ font-size: 64px;
+ color: $scorpion;
+ margin-top: 12px;
+ line-height: 60px;
+ text-align: center;
+ font-family: 'DIN NEXT Light';
+}
+
+.confirm-screen-send-amount-currency {
+ font-size: 20px;
+ line-height: 20px;
+ text-align: center;
+}
+
+.confirm-memo-wrapper {
+ min-height: 24px;
+ width: 100%;
+ border-bottom: 1px solid $alto;
+}
+
+.confirm-screen-send-memo {
+ color: $dusty-gray;
+ font-size: 16px;
+ line-height: 24px;
+ text-align: center;
+ margin-top: 21px;
+ margin-bottom: 18px;
+ font-family: 'DIN NEXT Light';
+}
+
+.confirm-screen-label {
+ font-size: 18px;
+ line-height: 40px;
+ color: $scorpion;
+ text-align: left;
+}
+
+section .confirm-screen-account-name,
+section .confirm-screen-account-number,
+.confirm-screen-row-info,
+.confirm-screen-row-detail {
+ text-align: left;
+}
+
+.confirm-screen-rows {
+ display: flex;
+ flex-flow: column nowrap;
+ width: 100%;
+ padding: 0 24px 32px;
+}
+
+.confirm-screen-section-column {
+ flex: .5;
+}
+
+.confirm-screen-row {
+ display: flex;
+ flex-flow: row nowrap;
+ border-bottom: 1px solid $alto;
+ width: calc(100% - 24px);
+ align-items: center;
+ padding: 12px 0;
+ margin: 0 12px;
+}
+
+.confirm-screen-row-detail {
+ font-size: 12px;
+ line-height: 16px;
+ color: $dusty-gray;
+ font-family: 'DIN NEXT Light';
+}
+
+.confirm-screen-total-box {
+ background-color: $wild-sand;
+ border-radius: 8px;
+ padding: 22px 14px;
+ margin-top: 13px;
+
+ .confirm-screen-label {
+ line-height: 18px;
+ }
+
+ .confirm-screen-row-detail {
+ color: $scorpion;
+ }
+
+ &__subtitle {
+ font-size: 14px;
+ line-height: 20px;
+ font-family: 'DIN NEXT Light';
+ }
+}
+
+.confirm-screen-confirm-button {
+ height: 62px;
+ width: 216.88px;
+ border-radius: 2px;
+ background-color: #02c9b1;
+ font-size: 16px;
+ color: $white;
+ text-align: center;
+ font-family: 'DIN NEXT';
+ padding-top: 15px;
+ padding-bottom: 15px;
+ margin-top: 23px;
+ border-width: 0;
+ box-shadow: none;
+}
+
+.btn-light.confirm-screen-cancel-button {
+ height: 62px;
+ width: 216.88px;
+ background: none;
+ border: none;
+ opacity: 1;
+ width: 8em;
+ font-family: 'DIN NEXT';
+ border-width: 0;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ font-size: 16px;
+ box-shadow: none;
+ cursor: pointer;
+}
+
+#pending-tx-form {
+ flex: 1 0 auto;
+}
diff --git a/ui/app/css/itcss/components/footer.scss b/ui/app/css/itcss/components/footer.scss
new file mode 100644
index 000000000..000a53eed
--- /dev/null
+++ b/ui/app/css/itcss/components/footer.scss
@@ -0,0 +1,4 @@
+.app-footer {
+ padding-bottom: 10px;
+ align-items: center;
+}
diff --git a/ui/app/css/itcss/components/header.scss b/ui/app/css/itcss/components/header.scss
new file mode 100644
index 000000000..ed569cb08
--- /dev/null
+++ b/ui/app/css/itcss/components/header.scss
@@ -0,0 +1,71 @@
+.app-header {
+ align-items: center;
+ visibility: visible;
+ background: $gallery;
+ position: relative;
+ z-index: $header-z-index;
+ display: flex;
+ flex-flow: column nowrap;
+
+ @media screen and (max-width: 575px) {
+ padding: 0 12px;
+ width: 100%;
+ box-shadow: 0 2px 2px 1px rgba(0, 0, 0, .08);
+ z-index: $mobile-header-z-index;
+ }
+
+ @media screen and (min-width: 576px) {
+ height: 14.4vh;
+ max-height: 97px;
+ }
+}
+
+.app-header-contents {
+ display: flex;
+ justify-content: space-between;
+ flex-flow: row nowrap;
+ width: 100%;
+ height: 6.9vh;
+
+ @media screen and (max-width: 575px) {
+ height: 100%;
+ }
+
+ @media screen and (min-width: 576px) {
+ width: 85vw;
+ }
+
+ @media screen and (min-width: 769px) {
+ width: 80vw;
+ }
+
+ @media screen and (min-width: 1281px) {
+ width: 65vw;
+ }
+}
+
+.app-header h1 {
+ font-family: 'Montserrat Regular';
+ text-transform: uppercase;
+ color: #22232c; // $shark
+}
+
+h2.page-subtitle {
+ font-family: 'Montserrat Regular';
+ text-transform: uppercase;
+ color: #aeaeae;
+ font-size: 1em;
+ margin: 12px;
+}
+
+.network-component-wrapper {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.left-menu-wrapper {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss
new file mode 100644
index 000000000..8f6731358
--- /dev/null
+++ b/ui/app/css/itcss/components/hero-balance.scss
@@ -0,0 +1,110 @@
+.hero-balance {
+
+ @media screen and (max-width: $break-small) {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ margin: .3em .9em 0;
+ height: 80vh;
+ max-height: 225px;
+ }
+
+ @media screen and (min-width: $break-large) {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ margin: 2.8em 2.37em .8em;
+ }
+
+ .balance-container {
+ display: flex;
+ margin: 0;
+ justify-content: flex-start;
+ align-items: center;
+
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ }
+
+ @media screen and (min-width: $break-large) {
+ flex-direction: row;
+ flex-grow: 3;
+ }
+ }
+
+ .balance-display {
+
+ @media screen and (max-width: $break-small) {
+ text-align: center;
+
+ .token-amount {
+ font-size: 175%;
+ margin-top: 12.5%;
+ }
+
+ .fiat-amount {
+ font-size: 115%;
+ margin-top: 8.5%;
+ color: #a0a0a0;
+ }
+ }
+
+ @media screen and (min-width: $break-large) {
+ margin-left: 3%;
+ justify-content: flex-start;
+ align-items: flex-start;
+
+ .token-amount {
+ font-size: 135%;
+ }
+
+ .fiat-amount {
+ margin-top: .25%;
+ font-size: 105%;
+ }
+ }
+ }
+
+ .balance-icon {
+ border-radius: 25px;
+ width: 45px;
+ height: 45px;
+ border: 1px solid $alto;
+ }
+
+ .hero-balance-buttons {
+
+ @media screen and (max-width: $break-small) {
+ width: 100%;
+ height: 100px; // needed a round number to set the heights of the buttons inside
+ }
+
+ @media screen and (min-width: $break-large) {
+ flex-grow: 2;
+ justify-content: flex-end;
+ }
+
+ button.btn-clear {
+ background: $white;
+ border: 1px solid;
+ border-radius: 2px;
+ font-size: 12px;
+
+ @media screen and (max-width: $break-small) {
+ width: 23%;
+ height: 55%;
+ border-color: $black;
+ }
+
+ @media screen and (min-width: $break-large) {
+ border-color: $curious-blue;
+ color: $curious-blue;
+ padding: 0;
+ width: 85px;
+ height: 34px;
+ }
+ }
+ }
+}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
new file mode 100644
index 000000000..63ac8bd47
--- /dev/null
+++ b/ui/app/css/itcss/components/index.scss
@@ -0,0 +1,29 @@
+@import './buttons.scss';
+
+@import './header.scss';
+
+@import './footer.scss';
+
+@import './network.scss';
+
+@import './modal.scss';
+
+@import './newui-sections.scss';
+
+@import './account-dropdown.scss';
+
+@import './send.scss';
+
+@import './confirm.scss';
+
+// Balances
+@import './hero-balance.scss';
+
+@import './wallet-balance.scss';
+
+// Tx List and Sections
+@import './transaction-list.scss';
+
+@import './sections.scss';
+
+@import './token-list.scss';
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
new file mode 100644
index 000000000..e9698ce5b
--- /dev/null
+++ b/ui/app/css/itcss/components/modal.scss
@@ -0,0 +1,363 @@
+.modal > div:focus {
+ outline: none !important;
+}
+
+// Buy Modal
+.buy-modal-content {
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ font-family: 'DIN OT';
+}
+
+@media screen and (max-width: 575px) {
+ .buy-modal-content-title-wrapper {
+ justify-content: space-around;
+ width: 100%;
+ height: 100px;
+ }
+
+ .buy-modal-content-title {
+ font-size: 26px;
+ margin-top: 15px;
+ }
+
+ .buy-modal-content-options {
+ flex-direction: column;
+ padding: 5% 33%;
+ }
+
+ .buy-modal-content-footer {
+ text-transform: uppercase;
+ width: 100%;
+ height: 50px;
+ }
+
+ div.buy-modal-content-option {
+ display: flex;
+ flex-direction: column;
+ width: 80vw;
+ height: 15vh;
+ margin: 10px;
+ text-align: center;
+ border-radius: 6px;
+ border: 1px solid $black;
+ padding: 0% 7%;
+ justify-content: space-around;
+
+ div.buy-modal-content-option-title {
+ font-size: 20px;
+ }
+
+ div.buy-modal-content-option-subtitle {
+ font-size: 16px;
+ }
+ }
+}
+
+@media screen and (min-width: 576px) {
+ .buy-modal-content-title-wrapper {
+ justify-content: space-around;
+ width: 100%;
+ height: 110px;
+ }
+
+ .buy-modal-content-title {
+ font-size: 26px;
+ margin-top: 15px;
+ }
+
+ .buy-modal-content-footer {
+ text-transform: uppercase;
+ width: 100%;
+ height: 50px;
+ }
+
+ .buy-modal-content-options {
+ flex-direction: row;
+ margin: 20px 0;
+ }
+
+ div.buy-modal-content-option {
+ display: flex;
+ flex-direction: column;
+ width: 20vw;
+ height: 18vw;
+ text-align: center;
+ border-radius: 6px;
+ border: 1px solid $black;
+ margin: 0 .5vw;
+ justify-content: space-around;
+
+ div.buy-modal-content-option-title {
+ font-size: 20px;
+
+ @media screen and (max-width: 679px) {
+ font-size: 14px;
+ }
+
+ @media screen and (min-width: 1281px) {
+ font-size: 26px;
+ }
+ }
+
+ div.buy-modal-content-option-subtitle {
+ font-size: 16px;
+ padding: 0 10px;
+ height: 25%;
+
+ @media screen and (max-width: 679px) {
+ font-size: 10px;
+ padding: 0 10px;
+ margin-bottom: 5px;
+ line-height: 15px;
+ }
+
+ @media screen and (min-width: 680px) {
+ font-size: 14px;
+ padding: 0 4px;
+ margin-bottom: 2px;
+ }
+
+ @media screen and (min-width: 1281px) {
+ font-size: 20px;
+ padding: 0;
+ }
+ }
+
+ div.buy-modal-content-footer {
+ margin-top: 8vh;
+ }
+ }
+}
+
+// Edit Account Name Modal
+.edit-account-name-modal-content {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ position: relative;
+}
+
+.edit-account-name-modal-cancel {
+ position: absolute;
+ top: 12px;
+ right: 20px;
+ font-size: 25px;
+}
+
+.edit-account-name-modal-title {
+ margin: 15px;
+}
+
+.edit-account-name-modal-save-button {
+ width: 33%;
+ height: 45px;
+ margin: 15px;
+ font-weight: 700;
+ margin-top: 25px;
+}
+
+.edit-account-name-modal-input {
+ width: 90%;
+ height: 50px;
+ text-align: left;
+ margin: 10px;
+ padding: 10px;
+ font-size: 18px;
+}
+
+// Account Details Modal
+.account-details-modal-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ position: relative;
+ padding: 5px 0 31px 0;
+ border: 1px solid $silver;
+ border-radius: 4px;
+ font-family: 'Montserrat UltraLight';
+
+ button {
+ cursor: pointer;
+ }
+}
+
+.account-details-modal-wrapper .qr-header {
+ margin-top: 9px;
+ font-size: 20px;
+}
+
+.account-details-modal-wrapper .qr-wrapper {
+ margin-top: 5px;
+}
+
+.account-details-modal-wrapper .ellip-address-wrapper {
+ display: flex;
+ justify-content: center;
+ border: 1px solid $alto;
+ padding: 5px 10px;
+ font-family: 'Montserrat Light';
+ margin-top: 7px;
+ width: 286px;
+}
+
+.account-details-modal-wrapper .qr-ellip-address {
+ width: 254px;
+ border: none;
+ font-family: 'Montserrat Light';
+ font-size: 14px;
+}
+
+.account-details-modal-wrapper .btn-clear {
+ min-height: 28px;
+ font-size: 14px;
+ border-color: $curious-blue;
+ color: $curious-blue;
+ border-radius: 2px;
+ flex-basis: 100%;
+ width: 75%;
+ margin-top: 17px;
+ padding: 10px 22px;
+ height: 44px;
+ width: 235px;
+ font-family: 'Montserrat Light';
+}
+
+.account-details-modal-divider {
+ width: 100%;
+ height: 1px;
+ margin: 19px 0 8px 0;
+ background-color: $alto;
+}
+
+.account-details-modal-wrapper .identicon {
+ position: relative;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ top: -32px;
+ margin-bottom: -32px;
+}
+
+.account-details-modal-close::after {
+ content: '\00D7';
+ font-size: 40px;
+ color: $dusty-gray;
+ position: absolute;
+ top: 10px;
+ right: 12px;
+ cursor: pointer;
+}
+
+// New Account Modal
+.new-account-modal-wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ position: relative;
+ border: 1px solid $alto;
+ box-shadow: 0 0 2px 2px $alto;
+ font-family: 'Montserrat Light';
+}
+
+.new-account-modal-header {
+ background: $wild-sand;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ padding: 30px;
+ font-size: 22px;
+ color: $nile-blue;
+ height: 79px;
+}
+
+.modal-close-x::after {
+ content: '\00D7';
+ font-size: 2em;
+ color: $dusty-gray;
+ position: absolute;
+ top: 25px;
+ right: 17.5px;
+ font-family: sans-serif;
+ cursor: pointer;
+}
+
+.new-account-modal-content {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ margin-top: 15px;
+ font-size: 17px;
+ color: $nile-blue;
+}
+
+.new-account-modal-content.after-input {
+ margin-top: 15px;
+ line-height: 25px;
+}
+
+.new-account-input-wrapper {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ padding-bottom: 2px;
+ margin-top: 13px;
+}
+
+.new-account-input {
+ padding: 15px;
+ padding-bottom: 20px;
+ border-radius: 8px;
+ border: 1px solid $alto;
+ width: 100%;
+ font-size: 1em;
+ color: $alto;
+ font-family: Montserrat Light;
+ font-size: 17px;
+ margin: 0 60px;
+}
+
+// For reference on below placeholder selectors: https://stackoverflow.com/questions/2610497/change-an-html5-inputs-placeholder-color-with-css
+.new-account-input::-webkit-input-placeholder {
+ color: $dusty-gray;
+}
+
+.new-account-input:-moz-placeholder {
+ color: $dusty-gray;
+ opacity: 1;
+}
+
+.new-account-input::-moz-placeholder {
+ color: $dusty-gray;
+ opacity: 1;
+}
+
+.new-account-input:-ms-input-placeholder {
+ color: $dusty-gray;
+}
+
+.new-account-input::-ms-input-placeholder {
+ color: $dusty-gray;
+}
+
+.new-account-modal-content.button {
+ margin-top: 22px;
+ margin-bottom: 30px;
+ width: 113px;
+ height: 44px;
+}
+
+.new-account-modal-wrapper .btn-clear {
+ font-size: 14px;
+ font-weight: 700;
+ background: $white;
+ border: 1px solid;
+ border-radius: 2px;
+ color: $tundora;
+ flex: 1;
+}
diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss
new file mode 100644
index 000000000..012b1faf6
--- /dev/null
+++ b/ui/app/css/itcss/components/network.scss
@@ -0,0 +1,110 @@
+.network-indicator {
+ display: flex;
+ align-items: center;
+ font-size: .6em;
+}
+
+.network-name {
+ line-height: 12px;
+ padding: 0 4px;
+ font-family: 'DIN OT';
+ font-size: 10px;
+ flex: 1 0 auto;
+}
+
+.network-droppo {
+ right: 2px;
+
+ @media screen and (min-width: 576px) {
+ right: calc(((100% - 85vw) / 2) + 2px);
+ }
+
+ @media screen and (min-width: 769px) {
+ right: calc(((100% - 80vw) / 2) + 2px);
+ }
+
+ @media screen and (min-width: 1281px) {
+ right: calc(((100% - 65vw) / 2) + 2px);
+ }
+}
+
+.network-name-item {
+ font-weight: 100;
+ flex: 1 0 auto;
+ color: $dusty-gray;
+}
+
+.network-check,
+.network-check__transparent {
+ color: $white;
+ margin-left: 7px;
+}
+
+.network-check__transparent {
+ opacity: 0;
+ width: 16px;
+ margin: 0;
+}
+
+.menu-icon-circle, .menu-icon-circle--active {
+ height: 23px;
+ width: 23px;
+ margin: 9px;
+ background: none;
+ border-radius: 22px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid transparent;
+ background: none;
+}
+
+.menu-icon-circle--active {
+ border: 1px solid white;
+ background: rgba(100, 100, 100, 0.4);
+}
+
+.menu-icon-circle div, .menu-icon-circle--active div {
+ height: 17px;
+ width: 17px;
+ border-radius: 17px;
+ opacity: 0.7;
+}
+
+.menu-icon-circle--active div {
+ opacity: 1;
+}
+
+.network-dropdown-header {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+}
+
+.network-dropdown-divider {
+ width: 100%;
+ height: 1px;
+ margin: 10px 0;
+ background-color: $scorpion;
+}
+
+.network-dropdown-title {
+ height: 25px;
+ width: 75px;
+ color: $white;
+ font-family: 'DIN OT';
+ font-size: 18px;
+ line-height: 25px;
+ text-align: center;
+}
+
+.network-dropdown-content {
+ height: 36px;
+ width: 265px;
+ color: $dusty-gray;
+ font-family: 'DIN OT';
+ font-size: 14px;
+ line-height: 18px;
+}
+
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
new file mode 100644
index 000000000..ae6ee6311
--- /dev/null
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -0,0 +1,179 @@
+/*
+ NewUI Container Elements
+ */
+
+// Component Colors
+$tx-view-bg: $white;
+$wallet-view-bg: $wild-sand;
+
+// Main container
+.main-container {
+ position: absolute;
+ z-index: $main-container-z-index;
+ font-family: "DIN OT Light";
+ display: flex;
+ flex-wrap: wrap;
+ align-items: stretch;
+}
+
+.main-container::-webkit-scrollbar {
+ display: none;
+}
+
+// tx view
+
+.tx-view {
+ flex: 63.5 0 66.5%;
+ background: $tx-view-bg;
+}
+
+// wallet view and sidebar
+
+.wallet-view {
+ display: flex;
+ flex-direction: column;
+ flex: 33.5 0 33.5%;
+ background: $wallet-view-bg;
+
+ @media screen and (min-width: 576px) {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ }
+
+ .wallet-view-account-details {
+ flex: 0 0 150px;
+ }
+}
+
+@media screen and (min-width: 576px) {
+ .wallet-view::-webkit-scrollbar {
+ display: none;
+ }
+}
+
+.wallet-view-title-wrapper {
+ flex: 0 0 25px;
+}
+
+.wallet-view-title {
+ margin-left: 15px;
+ font-size: 16px;
+
+ // No title on mobile
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+}
+
+.wallet-view.sidebar {
+ flex: 1 0 230px;
+ background: rgb(250, 250, 250);
+ z-index: $sidebar-z-index;
+ position: fixed;
+ top: 41px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ opacity: 1;
+ visibility: visible;
+ will-change: transform;
+ overflow-y: auto;
+ box-shadow: rgba(0, 0, 0, .15) 2px 2px 4px;
+ width: 85%;
+ height: 100%;
+}
+
+.sidebar-overlay {
+ z-index: $sidebar-overlay-z-index;
+ position: fixed;
+ top: 41px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ opacity: 1;
+ visibility: visible;
+ background-color: rgba(0, 0, 0, .3);
+}
+
+// main-container media queries
+
+@media screen and (min-width: 576px) {
+ .lap-visible {
+ display: flex;
+ }
+
+ .phone-visible {
+ display: none;
+ }
+
+ .main-container {
+ margin-top: 6.9vh;
+ width: 85%;
+ height: 90vh;
+ box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
+ }
+}
+
+@media screen and (min-width: 769px) {
+ .main-container {
+ margin-top: 6.9vh;
+ width: 80%;
+ height: 82vh;
+ box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
+ }
+}
+
+@media screen and (min-width: 1281px) {
+ .main-container {
+ margin-top: 6.9vh;
+ width: 65%;
+ height: 82vh;
+ box-shadow: 0 0 7px 0 rgba(0, 0, 0, .08);
+ }
+}
+
+@media screen and (max-width: 575px) {
+ .lap-visible {
+ display: none;
+ }
+
+ .phone-visible {
+ display: flex;
+ }
+
+ .main-container {
+ margin-top: 41px;
+ height: calc(100% - 41px);
+ width: 100%;
+ overflow-y: auto;
+ background-color: $white;
+ }
+
+ button.btn-clear {
+ width: 93px;
+ height: 50px;
+ font-size: .7em;
+ background: $white;
+ border: 1px solid;
+ }
+}
+
+// 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;
+ }
+}
+
+// account options dropdown
+.account-options-menu {
+ align-items: center;
+ justify-content: flex-start;
+ margin: 5% 7% 0%;
+}
diff --git a/ui/app/css/index.css b/ui/app/css/itcss/components/sections.scss
index 49b432a1f..44ec3e862 100644
--- a/ui/app/css/index.css
+++ b/ui/app/css/itcss/components/sections.scss
@@ -1,222 +1,26 @@
-/*
-faint orange (textfield shades) #FAF6F0
-light orange (button shades): #F5C26D
-dark orange (text): #F5A623
-borders/font/any gray: #4A4A4A
-*/
-
-/*
-application specific styles
-*/
-
-* {
- box-sizing: border-box;
-}
-
-html, body {
- font-family: 'Montserrat Regular', Arial;
- color: #4D4D4D;
- font-weight: 300;
- line-height: 1.4em;
- background: #F7F7F7;
- margin: 0;
- padding: 0;
-}
-
-html {
- min-height: 500px;
-}
-
-.app-root {
- overflow: hidden;
- position: relative
-}
-
-.app-primary {
- display: flex;
-}
-
-input:focus, textarea:focus {
- outline: none;
-}
-
-.full-size {
- height: 100%;
- width: 100%;
-}
-
-.full-width {
- width: 100%;
-}
-
-.full-height {
- height: 100%;
-}
-
-.full-flex-height {
- display: flex;
- flex: 1 1 auto;
- flex-direction: column;
-}
-
-#app-content {
- overflow-x: hidden;
- min-width: 357px;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-button, input[type="submit"] {
- font-family: 'Montserrat Bold';
- outline: none;
- cursor: pointer;
- padding: 8px 12px;
- border: none;
- color: white;
- transform-origin: center center;
- transition: transform 50ms ease-in;
- /* default orange */
- background: rgba(247, 134, 28, 1);
- box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36);
-}
-
-.btn-green, input[type="submit"].btn-green {
- background: rgba(106, 195, 96, 1);
- box-shadow: 0px 3px 6px rgba(106, 195, 96, 0.36);
-}
+// Old scss, do not lint - clean up later
+/* stylelint-disable */
-.btn-red {
- background: rgba(254, 35, 17, 1);
- box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36);
-}
-
-button[disabled], input[type="submit"][disabled] {
- cursor: not-allowed;
- background: rgba(197, 197, 197, 1);
- box-shadow: 0px 3px 6px rgba(197, 197, 197, 0.36);
-}
-
-button.spaced {
- margin: 2px;
-}
-
-button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover {
- transform: scale(1.1);
-}
-button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
- transform: scale(0.95);
-}
-
-a {
- text-decoration: none;
- color: inherit;
-}
-
-a:hover{
- color: #df6b0e;
-}
/*
-app
+App Sections
+ TODO: Move into separate files.
*/
-.active {
- color: #909090;
-}
-
-button.primary {
- padding: 8px 12px;
- background: #F7861C;
- box-shadow: 0px 3px 6px rgba(247, 134, 28, 0.36);
- color: white;
- font-size: 1.1em;
- font-family: 'Montserrat Regular';
- text-transform: uppercase;
-}
-
-button.btn-thin {
- border: 1px solid;
- border-color: #4D4D4D;
- color: #4D4D4D;
- background: rgb(255, 174, 41);
- border-radius: 4px;
- min-width: 200px;
- margin: 12px 0;
- padding: 6px;
- font-size: 13px;
-}
-
-.app-header {
- padding: 6px 8px;
-}
-
-.app-header h1 {
- font-family: 'Montserrat Regular';
- text-transform: uppercase;
- color: #AEAEAE;
-}
-
-h2.page-subtitle {
- font-family: 'Montserrat Regular';
- text-transform: uppercase;
- color: #AEAEAE;
- font-size: 1em;
- margin: 12px;
-}
-
-.app-footer {
- padding-bottom: 10px;
- align-items: center;
-}
-
-.identicon {
- height: 46px;
- width: 46px;
- background-size: cover;
- border-radius: 100%;
- border: 3px solid gray;
-}
-
+/* initialize */
textarea.twelve-word-phrase {
padding: 12px;
width: 300px;
height: 140px;
font-size: 16px;
- background: white;
+ background: $white;
resize: none;
}
-.network-indicator {
- display: flex;
- align-items: center;
- font-size: 0.6em;
-
-}
-
-.network-name {
- width: 5.2em;
- line-height: 9px;
- text-rendering: geometricPrecision;
-}
-
-.check {
- margin-left: 12px;
- color: #F7861C;
- flex: 1 0 auto;
- display: flex;
- justify-content: flex-end;
-}
-/*
-app sections
-*/
-
-/* initialize */
-
.initialize-screen hr {
width: 60px;
margin: 12px;
- border-color: #F7861C;
+ border-color: #f7861c;
border-style: solid;
}
@@ -235,11 +39,11 @@ app sections
/* unlock */
.error {
- color: #E20202;
+ color: #e20202;
}
.warning {
- color: #FFAE00;
+ color: #ffae00;
}
.lock {
@@ -249,9 +53,10 @@ app sections
.lock.locked {
transform: scale(1.5);
- opacity: 0.0;
+ opacity: 0;
transition: opacity 400ms ease-in, transform 400ms ease-in;
}
+
.lock.unlocked {
transform: scale(1);
opacity: 1;
@@ -262,15 +67,18 @@ app sections
transform: scaleX(1) translateX(0);
transition: transform 250ms ease-in;
}
+
.lock.unlocked .lock-top {
transform: scaleX(-1) translateX(-12px);
transition: transform 250ms ease-in;
}
+
.lock.unlocked:hover {
border-radius: 4px;
background: #e5e5e5;
border: 1px solid #b1b1b1;
}
+
.lock.unlocked:active {
background: #c3c3c3;
}
@@ -290,55 +98,46 @@ app sections
.unlock-screen input[type=password] {
width: 260px;
- /*height: 36px;
- margin-bottom: 24px;
- padding: 8px;*/
}
-.sizing-input{
+.sizing-input {
font-size: 14px;
height: 30px;
padding-left: 5px;
}
-.editable-label{
+
+.editable-label {
display: flex;
}
+
/* Webkit */
+
.unlock-screen input::-webkit-input-placeholder {
text-align: center;
font-size: 1.2em;
}
+
/* Firefox 18- */
+
.unlock-screen input:-moz-placeholder {
text-align: center;
font-size: 1.2em;
}
+
/* Firefox 19+ */
+
.unlock-screen input::-moz-placeholder {
text-align: center;
font-size: 1.2em;
}
+
/* IE */
+
.unlock-screen input:-ms-input-placeholder {
text-align: center;
font-size: 1.2em;
}
-input.large-input, textarea.large-input {
- /*margin-bottom: 24px;*/
- padding: 8px;
-}
-
-input.large-input {
- height: 36px;
-}
-
-.letter-spacey {
- letter-spacing: 0.1em;
-}
-
-
-
/* accounts */
.accounts-section {
@@ -346,7 +145,7 @@ input.large-input {
}
.accounts-section .horizontal-line {
- margin: 0px 18px;
+ margin: 0 18px;
}
.accounts-list-option {
@@ -363,7 +162,7 @@ input.large-input {
}
.unconftx-link .fa-arrow-right {
- margin: 0px -8px 0px 8px;
+ margin: 0 -8px 0px 8px;
}
/* identity panel */
@@ -390,7 +189,7 @@ input.large-input {
.identity-panel i {
margin-top: 32px;
margin-right: 6px;
- color: #B9B9B9;
+ color: #b9b9b9;
}
.identity-panel .arrow-right {
@@ -401,34 +200,33 @@ input.large-input {
}
.identity-copy.flex-column {
- flex: 0.25 0 auto;
+ flex: .25 0 auto;
justify-content: center;
}
/* accounts screen */
.identity-section {
-
}
.identity-section .identity-panel {
- background: #E9E9E9;
- border-bottom: 1px solid #B1B1B1;
+ background: #e9e9e9;
+ border-bottom: 1px solid #b1b1b1;
cursor: pointer;
}
.identity-section .identity-panel.selected {
- background: white;
- color: #F3C83E;
+ background: $white;
+ color: #f3c83e;
}
.identity-section .identity-panel.selected .identicon {
- border-color: orange;
+ border-color: $orange;
}
.identity-section .accounts-list-option:hover,
.identity-section .accounts-list-option.selected {
- background:white;
+ background: $white;
}
/* account detail screen */
@@ -444,15 +242,14 @@ input.large-input {
flex-grow: 10;
}
-.name-label{
-
+.name-label {
}
.unapproved-tx-icon {
height: 16px;
width: 16px;
background: rgb(47, 174, 244);
- border-color: #AEAEAE;
+ border-color: $silver-chalice;
border-radius: 13px;
}
@@ -460,6 +257,7 @@ input.large-input {
height: 100%;
visibility: hidden;
}
+
.editing-label {
display: flex;
justify-content: flex-start;
@@ -467,8 +265,9 @@ input.large-input {
margin-bottom: 2px;
font-size: 11px;
text-rendering: geometricPrecision;
- color: #F7861C;
+ color: #f7861c;
}
+
.name-label:hover .edit-text {
visibility: visible;
}
@@ -480,47 +279,32 @@ input.large-input {
margin: 12px;
margin-bottom: 24px;
border-radius: 4px;
- border: 2px solid #F3C83E;
- background: #FAF6F0;
-}
-
-/* Send Screen */
-
-.send-screen {
-
-}
-
-.send-screen section {
- margin: 8px 16px;
-}
-
-.send-screen input {
- width: 100%;
- font-size: 12px;
+ border: 2px solid #f3c83e;
+ background: #faf6f0;
}
/* Ether Balance Widget */
.ether-balance-amount {
- color: #F7861C;
+ color: #f7861c;
}
.ether-balance-label {
- color: #ABA9AA;
+ color: #aba9aa;
}
/* Info screen */
-.info-gray{
+.info-gray {
font-family: 'Montserrat Regular';
text-transform: uppercase;
- color: #AEAEAE;
+ color: $silver-chalice;
}
-.icon-size{
+.icon-size {
width: 20px;
}
-.info{
+.info {
font-family: 'Montserrat Regular', Arial;
padding-bottom: 10px;
display: inline-block;
@@ -533,7 +317,6 @@ input.large-input {
align-items: center;
}
-
.custom-radio-selected {
width: 17px;
height: 17px;
@@ -542,7 +325,7 @@ input.large-input {
border-radius: 15px;
border-width: 5px;
background: rgba(247, 134, 28, 1);
- border-color: #F7F7F7;
+ border-color: #f7f7f7;
}
.custom-radio-inactive {
@@ -551,38 +334,26 @@ input.large-input {
border: solid;
border-width: 1px;
border-radius: 24px;
- border-color: #AEAEAE;
+ border-color: $silver-chalice;
}
.radio-titles {
color: rgba(247, 134, 28, 1);
}
-.radio-titles-subtext {
-
-}
-
-.selected-exchange {
-
-}
-
-.buy-radio {
-
-}
-
-.eth-warning{
+.eth-warning {
transition: opacity 400ms ease-in, transform 400ms ease-in;
}
-.buy-subview{
+.buy-subview {
transition: opacity 400ms ease-in, transform 400ms ease-in;
}
-.input-container:hover .edit-text{
+.input-container:hover .edit-text {
visibility: visible;
}
-.buy-inputs{
+.buy-inputs {
font-family: 'Montserrat Light';
font-size: 13px;
height: 20px;
@@ -590,33 +361,32 @@ input.large-input {
box-sizing: border-box;
border: solid;
border-color: transparent;
- border-width: 0.5px;
+ border-width: .5px;
border-radius: 2px;
-
}
-.input-container:hover .buy-inputs{
+
+.input-container:hover .buy-inputs {
box-sizing: inherit;
border: solid;
- border-color: #F7861C;
- border-width: 0.5px;
+ border-color: #f7861c;
+ border-width: .5px;
border-radius: 2px;
}
-.buy-inputs:focus{
+.buy-inputs:focus {
border: solid;
- border-color: #F7861C;
- border-width: 0.5px;
+ border-color: #f7861c;
+ border-width: .5px;
border-radius: 2px;
}
.activeForm {
- background: #F7F7F7;
+ background: #f7f7f7;
border: none;
border-radius: 8px 8px 0px 0px;
width: 50%;
text-align: center;
padding-bottom: 4px;
-
}
.inactiveForm {
@@ -635,12 +405,12 @@ input.large-input {
width: 118px;
height: 42px;
padding: 1px;
- color: #4D4D4D;
+ color: #4d4d4d;
}
-.marketinfo{
+.marketinfo {
font-family: 'Montserrat light';
- color: #AEAEAE;
+ color: $silver-chalice;
font-size: 15px;
line-height: 17px;
}
@@ -655,52 +425,50 @@ input.large-input {
overflow: scroll;
}
-.icon-control .fa-refresh{
+.icon-control .fa-refresh {
visibility: hidden;
}
-.icon-control:hover .fa-refresh{
+.icon-control:hover .fa-refresh {
visibility: visible;
}
-.icon-control:hover .fa-chevron-right{
+.icon-control:hover .fa-chevron-right {
visibility: hidden;
}
.inactive {
- color: #AEAEAE;
+ color: $silver-chalice;
}
-.inactive button{
- background: #AEAEAE;
- color: white;
+.inactive button {
+ background: $silver-chalice;
+ color: $white;
}
-.ellip-address {
+.qr-ellip-address {
overflow: hidden;
text-overflow: ellipsis;
- width: 5em;
- font-size: 14px;
- font-family: "Montserrat Light";
- margin-left: 5px;
}
.qr-header {
- font-size: 25px;
- margin-top: 40px;
+ font-size: 25px;
+ margin-top: 40px;
}
.qr-message {
font-size: 12px;
- color: #F7861C;
+ color: #f7861c;
}
div.message-container > div:first-child {
margin-top: 18px;
font-size: 15px;
- color: #4D4D4D;
+ color: #4d4d4d;
}
.pop-hover:hover {
- transform: scale(1.1);
+ transform: scale(1.1);
}
+
+/* stylelint-enable */
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
new file mode 100644
index 000000000..2d6374aa2
--- /dev/null
+++ b/ui/app/css/itcss/components/send.scss
@@ -0,0 +1,374 @@
+.send-screen-wrapper {
+ display: flex;
+ flex-flow: column nowrap;
+ z-index: 25;
+ font-family: 'DIN OT';
+
+ @media screen and (max-width: $break-small) {
+ width: 100%;
+ overflow-y: auto;
+ }
+
+ section {
+ flex: 0 0 auto;
+ }
+}
+
+.send-screen-card {
+ background-color: #fff;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .08);
+ padding: 46px 40.5px 26px;
+ position: relative;
+ top: -26px;
+ align-items: center;
+ display: flex;
+ flex-flow: column nowrap;
+ width: 498px;
+ flex: 1 0 auto;
+
+ @media screen and (max-width: $break-small) {
+ top: 0;
+ width: 100%;
+ box-shadow: none;
+ padding: 12px;
+ }
+}
+
+/* Send Screen */
+
+.send-screen section {
+ margin: 4px 16px;
+}
+
+.send-screen input {
+ width: 100%;
+ font-size: 12px;
+}
+
+.send-eth-icon {
+ border-radius: 50%;
+ width: 70px;
+ height: 70px;
+ border: 1px solid $alto;
+ box-shadow: 0 0 4px 0 rgba(0, 0, 0, .2);
+ position: absolute;
+ top: -35px;
+ z-index: 25;
+ padding: 4px;
+ background-color: $white;
+
+ @media screen and (max-width: $break-small) {
+ position: relative;
+ top: 0;
+ }
+}
+
+.send-screen-input-wrapper {
+ width: 95%;
+ position: relative;
+
+ .fa-bolt {
+ padding-right: 4px;
+ }
+
+ .large-input {
+ border: 1px solid $dusty-gray;
+ border-radius: 4px;
+ margin: 4px 0 20px;
+ font-size: 16px;
+ line-height: 22.4px;
+ font-family: "DIN OT";
+ }
+
+ .send-screen-gas-input {
+ border: 1px solid transparent;
+ }
+
+ &__error-message {
+ display: none;
+ }
+
+ &--error {
+ input,
+ .send-screen-gas-input {
+ border-color: $red !important;
+ }
+
+ .send-screen-input-wrapper__error-message {
+ display: block;
+ position: absolute;
+ bottom: 4px;
+ font-size: 12px;
+ line-height: 12px;
+ left: 8px;
+ color: $red;
+ }
+ }
+}
+
+.send-screen-input {
+ width: 100%;
+}
+
+.send-screen-gas-input {
+ width: 100%;
+ height: 41px;
+ border-radius: 3px;
+ background-color: #f3f3f3;
+ border-width: 0;
+ border-style: none;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-left: 10px;
+ padding-right: 12px;
+ font-size: 16px;
+ color: $scorpion;
+}
+
+.send-screen-amount-labels {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.send-screen-gas-labels {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.currency-toggle {
+ &__item {
+ color: $curious-blue;
+ cursor: pointer;
+
+ &--selected {
+ color: $black;
+ cursor: default;
+ }
+ }
+}
+
+.send-screen-gas-input-customize {
+ color: $curious-blue;
+ font-size: 12px;
+ cursor: pointer;
+}
+
+.gas-tooltip-close-area {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+}
+
+.customize-gas-tooltip-container {
+ position: absolute;
+ bottom: 50px;
+ width: 237px;
+ height: 307px;
+ background-color: $white;
+ opacity: 1;
+ box-shadow: $alto 0 0 5px;
+ z-index: 1050;
+ padding: 13px 19px;
+ font-size: 16px;
+ border-radius: 4px;
+ font-family: "Lato";
+ font-weight: 500;
+}
+
+.gas-tooltip-arrow {
+ height: 25px;
+ width: 25px;
+ z-index: 1200;
+ background: $white;
+ position: absolute;
+ transform: rotate(45deg);
+ left: 107px;
+ top: 294px;
+ box-shadow: 2px 2px 2px $alto;
+}
+
+.customize-gas-tooltip-container input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ display: none;
+}
+
+.customize-gas-tooltip-container input[type="number"]:hover::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ display: none;
+}
+
+.customize-gas-tooltip {
+ position: relative;
+}
+
+.gas-tooltip {
+ display: flex;
+ justify-content: center;
+}
+
+.gas-tooltip-label {
+ font-size: 16px;
+ color: $tundora;
+}
+
+.gas-tooltip-header {
+ padding-bottom: 12px;
+}
+
+.gas-tooltip-input-label {
+ margin-bottom: 5px;
+}
+
+.gas-tooltip-input-label i {
+ color: $silver-chalice;
+ margin-left: 6px;
+}
+
+.customize-gas-input {
+ width: 178px;
+ height: 28px;
+ border: 1px solid $alto;
+ font-size: 16px;
+ color: $nile-blue;
+ padding-left: 8px;
+}
+
+.customize-gas-input-wrapper {
+ position: relative;
+}
+
+.gas-tooltip-input-detail {
+ position: absolute;
+ top: 4px;
+ right: 26px;
+ font-size: 12px;
+ color: $silver-chalice;
+}
+
+.gas-tooltip-input-arrows {
+ position: absolute;
+ top: 0;
+ left: 178px;
+ width: 17px;
+ height: 28px;
+ border: 1px solid #dadada;
+ border-left: 0;
+ display: flex;
+ flex-direction: column;
+ color: #9b9b9b;
+ font-size: .8em;
+ padding: 1px 4px;
+}
+
+.token-gas {
+ &__amount {
+ display: inline-block;
+ margin-right: 4px;
+ }
+
+ &__symbol {
+ display: inline-block;
+ }
+}
+
+.send-screen {
+ &__title {
+ color: $scorpion;
+ font-size: 18px;
+ line-height: 29px;
+ }
+
+ &__subtitle {
+ margin: 10px 0 20px;
+ font-size: 14px;
+ line-height: 24px;
+ }
+
+ &__send-button,
+ &__cancel-button {
+ width: 163px;
+ text-align: center;
+ }
+}
+
+.send-token {
+ display: flex;
+ flex-flow: column nowrap;
+ z-index: 25;
+ font-family: "Montserrat Light";
+
+ &__content {
+ width: 498px;
+ height: 605px;
+ background-color: #fff;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .08);
+ padding: 46px 40.5px 26px;
+ position: relative;
+ top: -26px;
+ align-items: center;
+ display: flex;
+ flex-flow: column nowrap;
+ flex: 1 0 auto;
+
+ @media screen and (max-width: $break-small) {
+ top: 0;
+ width: 100%;
+ box-shadow: none;
+ padding: 12px;
+ }
+ }
+
+ .identicon {
+ position: absolute;
+ top: -35px;
+ z-index: 25;
+
+ @media screen and (max-width: $break-small) {
+ position: relative;
+ top: 0;
+ flex: 0 0 auto;
+ }
+ }
+
+ &__title {
+ color: $scorpion;
+ font-size: 18px;
+ line-height: 29px;
+ }
+
+ &__description,
+ &__balance-text,
+ &__token-symbol {
+ margin-top: 10px;
+ font-size: 14px;
+ line-height: 24px;
+ text-align: center;
+ }
+
+ &__token-balance {
+ font-size: 40px;
+ line-height: 40px;
+ margin-top: 13px;
+ }
+
+ &__button-group {
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ flex: 0 0 auto;
+
+ @media screen and (max-width: $break-small) {
+ margin-top: 24px;
+ }
+
+ button {
+ width: 163px;
+ }
+ }
+}
diff --git a/ui/app/css/itcss/components/token-list.scss b/ui/app/css/itcss/components/token-list.scss
new file mode 100644
index 000000000..9a772f666
--- /dev/null
+++ b/ui/app/css/itcss/components/token-list.scss
@@ -0,0 +1,46 @@
+$wallet-balance-breakpoint: 890px;
+$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
+
+.token-list-item {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 20px 24px;
+ cursor: pointer;
+ transition: linear 200ms;
+ background-color: rgba($wallet-balance-bg, 0);
+
+ &__token-balance {
+ font-size: 130%;
+
+ @media #{$wallet-balance-breakpoint-range} {
+ font-size: 105%;
+ }
+ }
+
+ &__fiat-amount {
+ margin-top: .25%;
+ font-size: 105%;
+
+ @media #{$wallet-balance-breakpoint-range} {
+ font-size: 95%;
+ }
+ }
+
+ @media #{$wallet-balance-breakpoint-range} {
+ padding: 10% 4%;
+ }
+
+ &--active {
+ background-color: rgba($wallet-balance-bg, 1);
+ }
+
+ &__identicon {
+ margin-right: 15px;
+ border: '1px solid #dedede';
+
+ @media #{$wallet-balance-breakpoint-range} {
+ margin-right: 4%;
+ }
+ }
+}
diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss
new file mode 100644
index 000000000..e3fe1a8b3
--- /dev/null
+++ b/ui/app/css/itcss/components/transaction-list.scss
@@ -0,0 +1,257 @@
+.tx-list-container {
+ height: 87.5%;
+
+ @media screen and (min-width: $break-large) {
+ overflow-y: scroll;
+ }
+}
+
+.tx-list-header {
+ text-transform: capitalize;
+}
+
+@media screen and (max-width: $break-small) {
+ .tx-list-header-wrapper {
+ margin-top: .2em;
+ margin-bottom: .6em;
+ // TODO: Resolve Layout Conflicst in Wallet View
+ // - This fixes txlist "transactions" title dispay
+ // margin-top: 0.2em;
+ // margin-bottom: 0.6em;
+ justify-content: center;
+ }
+
+ .tx-list-header {
+ align-self: center;
+ font-size: 12px;
+ color: $dusty-gray;
+ }
+}
+
+@media screen and (min-width: $break-large) {
+ .tx-list-header-wrapper {
+ flex: 0 0 55px;
+ }
+
+ .tx-list-header {
+ font-size: 16px;
+ margin: 1.5em 2.37em;
+ }
+
+ .tx-list-container::-webkit-scrollbar {
+ display: none;
+ }
+}
+
+.tx-list-content-divider {
+ height: 1px;
+ background: rgb(231, 231, 231);
+ flex: 0 0 1px;
+
+ @media screen and (max-width: $break-small) {
+ margin: .1em 0;
+ }
+
+ @media screen and (min-width: $break-large) {
+ margin: .1em 2.37em;
+ }
+}
+
+.tx-list-item-wrapper {
+ flex: 1 1 auto;
+ width: 0;
+ align-items: stretch;
+ justify-content: flex-start;
+ display: flex;
+ flex-flow: column nowrap;
+
+ @media screen and (max-width: $break-small) {
+ padding: 0 1.3em;
+ }
+
+ @media screen and (min-width: $break-large) {
+ padding-bottom: 12px;
+ }
+}
+
+.tx-list-clickable {
+ cursor: pointer;
+
+ &:hover {
+ background: rgba($alto, .2);
+ }
+}
+
+.tx-list-pending-item-container {
+ cursor: pointer;
+ opacity: .5;
+}
+
+.tx-list-date-wrapper {
+ flex: 1 1 auto;
+
+ @media screen and (max-width: $break-small) {
+ margin-top: 6px;
+ }
+
+ @media screen and (min-width: $break-large) {
+ margin-top: 12px;
+ }
+}
+
+.tx-list-content-wrapper {
+ align-items: stretch;
+ margin-bottom: 4px;
+ margin-top: 2px;
+ flex: 1 0 auto;
+ width: 100%;
+ display: flex;
+ flex-flow: row nowrap;
+
+ @media screen and (max-width: $break-small) {
+ font-size: 12px;
+
+ .tx-list-status {
+ font-size: 12px !important;
+ }
+
+ .tx-list-account {
+ font-size: 14px !important;
+ }
+
+ .tx-list-value {
+ font-size: 14px;
+ }
+
+ .tx-list-fiat-value {
+ font-size: 12px;
+ }
+ }
+}
+
+.tx-list-date {
+ color: $dusty-gray;
+ font-size: 12px;
+ font-family: "Montserrat UltraLight";
+}
+
+.tx-list-identicon-wrapper {
+ align-self: center;
+ flex: 0 0 auto;
+ margin-right: 16px;
+}
+
+.tx-list-account-and-status-wrapper {
+ display: flex;
+ flex: 1 1 auto;
+ flex-flow: row wrap;
+ width: 0;
+
+ @media screen and (max-width: $break-small) {
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ align-self: center;
+ }
+
+ @media screen and (min-width: $break-large) {
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+
+ .tx-list-account-wrapper {
+ flex: 1.3 2 auto;
+ min-width: 153px;
+ }
+
+ .tx-list-status-wrapper {
+ flex: 6 6 auto;
+ }
+ }
+
+ .tx-list-account {
+ font-size: 16px;
+ color: $scorpion;
+ }
+
+ .tx-list-status {
+ color: $dusty-gray;
+ font-size: 16px;
+ text-transform: capitalize;
+ }
+
+ .tx-list-status--rejected,
+ .tx-list-status--failed {
+ color: $monzo;
+ }
+}
+
+.tx-list-item {
+ border-top: 1px solid rgb(231, 231, 231);
+ flex: 0 0 auto;
+ display: flex;
+ flex-flow: row nowrap;
+
+ @media screen and (max-width: $break-small) {
+ // margin: 0 1.3em .95em; !important
+ }
+
+ @media screen and (min-width: $break-large) {
+ margin: 0 2.37em;
+ }
+
+ &:last-of-type {
+ border-bottom: 1px solid rgb(231, 231, 231);
+ margin-bottom: 32px;
+ }
+
+ &__wrapper {
+ align-self: center;
+ flex: 2 2 auto;
+ color: $dusty-gray;
+
+ .tx-list-value {
+ font-size: 16px;
+ text-align: right;
+ }
+
+ .tx-list-value--confirmed {
+ color: $caribbean-green;
+ }
+
+ .tx-list-fiat-value {
+ font-size: 12px;
+ text-align: right;
+ }
+ }
+
+ &--empty {
+ text-align: center;
+ border-bottom: none !important;
+ padding: 16px;
+ }
+}
+
+.tx-list-details-wrapper {
+ overflow: hidden;
+ flex: 0 0 35%;
+}
+
+.tx-list-value {
+ font-size: 16px;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.tx-list-fiat-value {
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.tx-list-value--confirmed {
+ color: $caribbean-green;
+}
diff --git a/ui/app/css/itcss/components/wallet-balance.scss b/ui/app/css/itcss/components/wallet-balance.scss
new file mode 100644
index 000000000..cd44f89bb
--- /dev/null
+++ b/ui/app/css/itcss/components/wallet-balance.scss
@@ -0,0 +1,70 @@
+$wallet-balance-bg: $gallery;
+$wallet-balance-breakpoint: 890px;
+$wallet-balance-breakpoint-range: "screen and (min-width: #{$break-large}) and (max-width: #{$wallet-balance-breakpoint})";
+
+.wallet-balance-wrapper {
+ flex: 0 0 auto;
+ transition: linear 200ms;
+ background: rgba($wallet-balance-bg, 0);
+
+ &--active {
+ background: rgba($wallet-balance-bg, 1);
+ }
+}
+
+.wallet-balance {
+ background: inherit;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ flex: 0 0 auto;
+ cursor: pointer;
+
+ .balance-container {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ margin: 20px 24px;
+ flex-direction: row;
+ flex-grow: 3;
+
+ @media #{$wallet-balance-breakpoint-range} {
+ margin: 10% 4%;
+ }
+ }
+
+ .balance-display {
+ margin-left: 15px;
+ justify-content: flex-start;
+ align-items: flex-start;
+
+ .token-amount {
+ font-size: 135%;
+ }
+
+ .fiat-amount {
+ margin-top: .25%;
+ font-size: 105%;
+ }
+
+ @media #{$wallet-balance-breakpoint-range} {
+ margin-left: 4%;
+
+ .token-amount {
+ font-size: 105%;
+ }
+
+ .fiat-amount {
+ font-size: 95%;
+ }
+ }
+ }
+
+ .balance-icon {
+ border-radius: 25px;
+ width: 45px;
+ height: 45px;
+ border: 1px solid $alto;
+ }
+}
diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss
new file mode 100644
index 000000000..51b7cf789
--- /dev/null
+++ b/ui/app/css/itcss/generic/index.scss
@@ -0,0 +1,71 @@
+/*
+ Generic
+ */
+
+@import './reset.scss';
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ font-family: 'Montserrat Regular', Arial;
+ color: #4d4d4d;
+ font-weight: 300;
+ line-height: 1.4em;
+ background: #f7f7f7;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ min-height: 500px;
+}
+
+.app-root {
+ overflow: hidden;
+ position: relative;
+}
+
+.app-primary {
+ display: flex;
+}
+
+input:focus,
+textarea:focus {
+ outline: none;
+}
+
+/* stylelint-disable */
+#app-content {
+ overflow-x: hidden;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: $break-small) {
+ background-color: $white;
+ }
+}
+/* stylelint-enable */
+
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+a:hover {
+ color: #df6b0e;
+}
+
+input.large-input,
+textarea.large-input {
+ padding: 8px;
+}
+
+input.large-input {
+ height: 36px;
+}
diff --git a/ui/app/css/itcss/generic/reset.scss b/ui/app/css/itcss/generic/reset.scss
new file mode 100644
index 000000000..e054d533e
--- /dev/null
+++ b/ui/app/css/itcss/generic/reset.scss
@@ -0,0 +1,147 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ /* stylelint-disable */
+ font: inherit;
+ /* stylelint-enable */
+ vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+
+/* stylelint-disable */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+
+body {
+ line-height: 1;
+}
+
+ol,
+ul {
+ list-style: none;
+}
+
+blockquote,
+q {
+ quotes: none;
+}
+
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+ content: '';
+ content: none;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+button {
+ border-style: none;
+ cursor: pointer;
+}
+
+/* stylelint-enable */
diff --git a/ui/app/css/itcss/objects/index.scss b/ui/app/css/itcss/objects/index.scss
new file mode 100644
index 000000000..220775682
--- /dev/null
+++ b/ui/app/css/itcss/objects/index.scss
@@ -0,0 +1 @@
+// Objects
diff --git a/ui/app/css/itcss/settings/index.scss b/ui/app/css/itcss/settings/index.scss
new file mode 100644
index 000000000..58a7ca7b7
--- /dev/null
+++ b/ui/app/css/itcss/settings/index.scss
@@ -0,0 +1,3 @@
+@import './variables.scss';
+
+@import './typography.scss';
diff --git a/ui/app/css/itcss/settings/typography.scss b/ui/app/css/itcss/settings/typography.scss
new file mode 100644
index 000000000..5b7817651
--- /dev/null
+++ b/ui/app/css/itcss/settings/typography.scss
@@ -0,0 +1,71 @@
+@import url('https://fonts.googleapis.com/css?family=Roboto:300,500');
+
+@import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css');
+
+@font-face {
+ font-family: 'Montserrat Regular';
+ src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff');
+ src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+ font-size: 'small';
+}
+
+@font-face {
+ font-family: 'Montserrat Bold';
+ src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff');
+ src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Montserrat Light';
+ src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff');
+ src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Montserrat UltraLight';
+ src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff');
+ src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'DIN OT';
+ src: url('/fonts/DIN_OT/DINOT-2.otf') format('opentype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'DIN OT Light';
+ src: url('/fonts/DIN_OT/DINOT-2.otf') format('opentype');
+ font-weight: 200;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'DIN NEXT';
+ src: url('/fonts/DIN NEXT/DIN NEXT W01 Regular.otf') format('opentype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'DIN NEXT Light';
+ src: url('/fonts/DIN NEXT/DIN NEXT W10 Light.otf') format('opentype');
+ font-weight: 400;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Lato';
+ src: url('/fonts/Lato/Lato-Regular.ttf') format('truetype');
+ font-weight: 400;
+ font-style: normal;
+}
diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss
new file mode 100644
index 000000000..624b301d1
--- /dev/null
+++ b/ui/app/css/itcss/settings/variables.scss
@@ -0,0 +1,69 @@
+/*
+ Variables
+ */
+
+// Base Colors
+$white: #fff;
+$black: #000;
+$orange: #ffa500;
+$red: #f00;
+
+/*
+ Colors
+ http://chir.ag/projects/name-that-color
+ */
+$white-linen: #faf6f0; // formerly 'faint orange (textfield shades)'
+$rajah: #f5c26d; // formerly 'light orange (button shades)'
+$buttercup: #f5a623; // formerly 'dark orange (text)'
+$tundora: #4a4a4a; // formerly 'borders/font/any gray'
+$gallery: #efefef;
+$alabaster: #f7f7f7;
+$shark: #22232c;
+$wild-sand: #f6f6f6;
+$white: #fff;
+$dusty-gray: #9b9b9b;
+$alto: #dedede;
+$alabaster: #fafafa;
+$silver-chalice: #aeaeae;
+$curious-blue: #2f9ae0;
+$concrete: #f3f3f3;
+$tundora: #4d4d4d;
+$nile-blue: #1b344d;
+$scorpion: #5d5d5d;
+$caribbean-green: #02C9B1;
+$silver: #cdcdcd;
+$caribbean-green: #02c9b1;
+$monzo: #d0021b;
+$crimson: #e91550;
+$blue-lagoon: #038789;
+$purple: #690496;
+$tulip-tree: #ebb33f;
+
+/*
+ Z-Indicies
+ */
+$dropdown-z-index: 30;
+$token-icon-z-index: 15;
+$container-z-index: 15;
+$header-z-index: 12;
+$mobile-header-z-index: 26;
+$main-container-z-index: 18;
+$send-card-z-index: 20;
+$sidebar-z-index: 26;
+$sidebar-overlay-z-index: 25;
+
+/*
+ Z Indicies - Current
+ app - 11
+ hex/bn as decimal input - 1 - remove?
+ dropdown - 11
+ loading - 10 - higher?
+ mascot - 0 - remove?
+ */
+
+/*
+ Responsive Breakpoints
+ */
+$break-small: 575px;
+$break-midpoint: 780px;
+$break-large: 576px;
diff --git a/ui/app/css/itcss/tools/index.scss b/ui/app/css/itcss/tools/index.scss
new file mode 100644
index 000000000..2236729e8
--- /dev/null
+++ b/ui/app/css/itcss/tools/index.scss
@@ -0,0 +1 @@
+@import './utilities.scss';
diff --git a/ui/app/css/lib.css b/ui/app/css/itcss/tools/utilities.scss
index f3acbee76..9f1caa732 100644
--- a/ui/app/css/lib.css
+++ b/ui/app/css/itcss/tools/utilities.scss
@@ -1,19 +1,34 @@
+/*
+ Utility Classes
+ */
+
/* color */
.color-orange {
- color: #F7861C;
+ color: #f7861c; // TODO: move to settings/variables
}
.color-forest {
- color: #0A5448;
+ color: #0a5448; // TODO: move to settings/variables
}
/* lib */
+.full-size {
+ height: 100%;
+ width: 100%;
+}
+
.full-width {
width: 100%;
}
+.full-flex-height {
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+}
+
.full-height {
height: 100%;
}
@@ -118,16 +133,19 @@
.pointer {
cursor: pointer;
}
+
.cursor-pointer {
cursor: pointer;
transform-origin: center center;
transition: transform 50ms ease-in-out;
}
+
.cursor-pointer:hover {
transform: scale(1.1);
}
+
.cursor-pointer:active {
- transform: scale(0.95);
+ transform: scale(.95);
}
.cursor-disabled {
@@ -147,7 +165,7 @@
}
.bold {
- font-weight: bold;
+ font-weight: 700;
}
.text-transform-uppercase {
@@ -172,12 +190,12 @@ hr.horizontal-line {
}
.hover-white:hover {
- background: white;
+ background: $white;
}
.red-dot {
- background: #E91550;
- color: white;
+ background: #e91550;
+ color: $white;
border-radius: 10px;
}
@@ -192,14 +210,14 @@ hr.horizontal-line {
}
.golden-square {
- background: #EBB33F;
+ background: #ebb33f;
}
.pending-dot {
- background: red;
+ background: $red;
left: 14px;
top: 14px;
- color: white;
+ color: $white;
border-radius: 10px;
height: 20px;
min-width: 20px;
@@ -214,10 +232,9 @@ hr.horizontal-line {
.keyring-label {
z-index: 1;
font-size: 11px;
- background: rgba(255,0,0,0.8);
- color: white;
- bottom: 0px;
- left: -8px;
+ background: rgba(255, 0, 0, .8);
+ bottom: -47px;
+ color: $white;
border-radius: 10px;
height: 20px;
min-width: 20px;
@@ -244,16 +261,13 @@ hr.horizontal-line {
margin: 13px;
}
-i.fa.fa-question-circle.fa-lg.menu-icon {
- font-size: 18px;
-}
-
.ether-icon {
background: rgb(0, 163, 68);
border-radius: 20px;
}
+
.testnet-icon {
- background: #2465E1;
+ background: #2465e1;
}
.drop-menu-item {
@@ -274,33 +288,26 @@ i.fa.fa-question-circle.fa-lg.menu-icon {
.critical-error {
text-align: center;
margin-top: 20px;
- color: red;
+ color: $red;
}
/*
- Hacky breakpoint fix for account + tab sections
- Resolves issue from @frankiebee in
- https://github.com/MetaMask/metamask-extension/pull/1835
- Please remove this when integrating new designs
+ Misc
*/
-@media screen and (min-width: 575px) and (max-width: 800px) {
- .account-data-subsection {
- flex: 0 0 auto !important; // reset flex
- margin-left: 10px !important; // create additional horizontal space
- margin-right: 10px !important;
- width: 40%;
- }
-
- .tabSection {
- flex: 0 0 auto !important;
- margin-left: 10px !important;
- margin-right: 10px !important;
- min-width: 285px;
- width: 49%;
- }
-
- .name-label {
- width: 80%;
- }
+// TODO: move into component-level contextual 'active' state
+.letter-spacey {
+ letter-spacing: .1em;
+}
+
+.active {
+ color: #909090;
+}
+
+.check {
+ margin-left: 7px;
+ color: #f7861c;
+ flex: 1 0 auto;
+ display: flex;
+ justify-content: flex-end;
}
diff --git a/ui/app/css/transitions.css b/ui/app/css/itcss/trumps/index.scss
index 393a944f9..d9a4202a4 100644
--- a/ui/app/css/transitions.css
+++ b/ui/app/css/itcss/trumps/index.scss
@@ -1,3 +1,9 @@
+/*
+ Trumps
+ */
+
+// Transitions
+
/* universal */
.app-primary .main-enter {
position: absolute;
@@ -8,7 +14,7 @@
.app-primary.from-right .main-enter-active,
.app-primary.from-left .main-enter-active {
overflow-x: hidden;
- transform: translateX(0px);
+ transform: translateX(0);
transition: transform 300ms ease-in;
}
@@ -17,18 +23,27 @@
transform: translateX(360px);
transition: transform 300ms ease-in;
}
+
.app-primary.from-right .main-leave-active {
transform: translateX(-360px);
transition: transform 300ms ease-in;
}
+.sidebar.from-left {
+ transform: translateX(-320px);
+ transition: transform 300ms ease-in;
+}
+
/* loader transitions */
-.loader-enter, .loader-leave-active {
- opacity: 0.0;
+.loader-enter,
+.loader-leave-active {
+ opacity: 0;
transition: opacity 150 ease-in;
}
-.loader-enter-active, .loader-leave {
- opacity: 1.0;
+
+.loader-enter-active,
+.loader-leave {
+ opacity: 1;
transition: opacity 150 ease-in;
}
@@ -36,7 +51,22 @@
.app-primary.from-right .main-enter:not(.main-enter-active) {
transform: translateX(360px);
}
+
.app-primary.from-left .main-enter:not(.main-enter-active) {
transform: translateX(-360px);
}
+i.fa.fa-question-circle.fa-lg.menu-icon {
+ font-size: 18px;
+}
+
+// This text is contained inside a div.
+// ID needed to override user agent stylesheet.
+// See components/modal.scss
+
+/* stylelint-disable */
+#buy-modal-content-footer-text {
+ font-family: 'DIN OT';
+ font-size: 16px;
+}
+/* stylelint-enable */
diff --git a/ui/app/css/reset.css b/ui/app/css/reset.css
deleted file mode 100644
index 9ce89e8bc..000000000
--- a/ui/app/css/reset.css
+++ /dev/null
@@ -1,48 +0,0 @@
-/* http://meyerweb.com/eric/tools/css/reset/
- v2.0 | 20110126
- License: none (public domain)
-*/
-
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-b, u, i, center,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td,
-article, aside, canvas, details, embed,
-figure, figcaption, footer, header, hgroup,
-menu, nav, output, ruby, section, summary,
-time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
-}
-/* HTML5 display-role reset for older browsers */
-article, aside, details, figcaption, figure,
-footer, header, hgroup, menu, nav, section {
- display: block;
-}
-body {
- line-height: 1;
-}
-ol, ul {
- list-style: none;
-}
-blockquote, q {
- quotes: none;
-}
-blockquote:before, blockquote:after,
-q:before, q:after {
- content: '';
- content: none;
-}
-table {
- border-collapse: collapse;
- border-spacing: 0;
-} \ No newline at end of file
diff --git a/ui/app/main-container.js b/ui/app/main-container.js
new file mode 100644
index 000000000..eaaff8517
--- /dev/null
+++ b/ui/app/main-container.js
@@ -0,0 +1,71 @@
+const Component = require('react').Component
+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 UnlockScreen = require('./unlock')
+
+module.exports = MainContainer
+
+inherits(MainContainer, Component)
+function MainContainer () {
+ Component.call(this)
+}
+
+MainContainer.prototype.render = function () {
+ // 3. summarize:
+ // switch statement goes inside MainContainer,
+ // or a method in renderPrimary
+ // - pass resulting h() to MainContainer
+ // - error checking in separate func
+ // - router in separate func
+ let contents = {
+ component: AccountAndTransactionDetails,
+ key: 'account-detail',
+ style: {},
+ }
+
+ if (this.props.isUnlocked === false) {
+ switch (this.props.currentViewName) {
+ case 'restoreVault':
+ log.debug('rendering restore vault screen')
+ contents = {
+ component: HDRestoreVaultScreen,
+ key: 'HDRestoreVaultScreen',
+ }
+ break
+ case 'config':
+ log.debug('rendering config screen from unlock screen.')
+ contents = {
+ component: ConfigScreen,
+ key: 'config',
+ }
+ break
+ default:
+ log.debug('rendering locked screen')
+ contents = {
+ component: UnlockScreen,
+ style: {
+ boxShadow: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ background: '#F7F7F7',
+ // must force 100%, because lock screen is full-width
+ width: '100%',
+ },
+ key: 'locked',
+ }
+ }
+ }
+
+ return h('div.main-container', {
+ style: contents.style,
+ }, [
+ h(contents.component, {
+ key: contents.key,
+ }, []),
+ ])
+}
+
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index 3a98d53a9..fbabad0ef 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -14,10 +14,6 @@ function reduceApp (state, action) {
if (selectedAddress) {
name = 'accountDetail'
}
- if (hasUnconfActions) {
- log.debug('pending txs detected, defaulting to conf-tx view.')
- name = 'confTx'
- }
var defaultView = {
name,
@@ -36,6 +32,14 @@ function reduceApp (state, action) {
var appState = extend({
shouldClose: false,
menuOpen: false,
+ modal: {
+ open: false,
+ modalState: {
+ name: null,
+ },
+ },
+ sidebarOpen: false,
+ networkDropdownOpen: false,
currentView: seedWords ? seedConfView : defaultView,
accountDetail: {
subview: 'transactions',
@@ -49,9 +53,48 @@ function reduceApp (state, action) {
}, state.appState)
switch (action.type) {
+ // dropdown methods
+ case actions.NETWORK_DROPDOWN_OPEN:
+ return extend(appState, {
+ networkDropdownOpen: true,
+ })
- // transition methods
+ case actions.NETWORK_DROPDOWN_CLOSE:
+ return extend(appState, {
+ networkDropdownOpen: false,
+ })
+
+ // sidebar methods
+ case actions.SIDEBAR_OPEN:
+ return extend(appState, {
+ sidebarOpen: true,
+ })
+
+ case actions.SIDEBAR_CLOSE:
+ return extend(appState, {
+ sidebarOpen: false,
+ })
+
+ // modal methods:
+ case actions.MODAL_OPEN:
+ return extend(appState, {
+ modal: Object.assign(
+ state.appState.modal,
+ { open: true },
+ { modalState: action.payload },
+ ),
+ })
+ case actions.MODAL_CLOSE:
+ return extend(appState, {
+ modal: Object.assign(
+ state.appState.modal,
+ { open: false },
+ { modalState: action.payload || state.appState.modal.modalState },
+ ),
+ })
+
+ // transition methods
case actions.TRANSITION_FORWARD:
return extend(appState, {
transForward: true,
@@ -133,7 +176,7 @@ function reduceApp (state, action) {
transForward: true,
})
- case actions.CREATE_NEW_VAULT_IN_PROGRESS:
+ case actions.CREATE_NEW_VAULT_IN_PROGRESS:
return extend(appState, {
currentView: {
name: 'createVault',
@@ -172,6 +215,16 @@ function reduceApp (state, action) {
warning: null,
})
+ case actions.SHOW_SEND_TOKEN_PAGE:
+ return extend(appState, {
+ currentView: {
+ name: 'sendToken',
+ context: appState.currentView.context,
+ },
+ transForward: true,
+ warning: null,
+ })
+
case actions.SHOW_NEW_KEYCHAIN:
return extend(appState, {
currentView: {
@@ -307,7 +360,7 @@ function reduceApp (state, action) {
return extend(appState, {
currentView: {
name: 'confTx',
- context: 0,
+ context: action.id ? indexForPending(state, action.id) : indexForLastPending(state),
},
transForward: action.transForward,
warning: null,
@@ -586,3 +639,7 @@ function indexForPending (state, txId) {
const index = unconfTxList.indexOf(match)
return index
}
+
+function indexForLastPending (state) {
+ return getUnconfActionList(state).length
+}
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index e0c416c2d..cdc98d05e 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -17,6 +17,8 @@ function reduceMetamask (state, action) {
lastUnreadNotice: undefined,
frequentRpcList: [],
addressBook: [],
+ selectedTokenAddress: null,
+ tokenExchangeRates: {},
}, state.metamask)
switch (action.type) {
@@ -115,6 +117,11 @@ function reduceMetamask (state, action) {
delete newState.seedWords
return newState
+ case actions.SET_SELECTED_TOKEN:
+ return extend(metamaskState, {
+ selectedTokenAddress: action.value,
+ })
+
case actions.SAVE_ACCOUNT_LABEL:
const account = action.value.account
const name = action.value.label
@@ -130,6 +137,15 @@ function reduceMetamask (state, action) {
conversionDate: action.value.conversionDate,
})
+ case actions.UPDATE_TOKEN_EXCHANGE_RATE:
+ const { payload: { pair, marketinfo } } = action
+ return extend(metamaskState, {
+ tokenExchangeRates: {
+ ...metamaskState.tokenExchangeRates,
+ [pair]: marketinfo,
+ },
+ })
+
default:
return metamaskState
diff --git a/ui/app/selectors.js b/ui/app/selectors.js
new file mode 100644
index 000000000..9a8bf5c7e
--- /dev/null
+++ b/ui/app/selectors.js
@@ -0,0 +1,61 @@
+const valuesFor = require('./util').valuesFor
+
+const selectors = {
+ getSelectedAddress,
+ getSelectedIdentity,
+ getSelectedAccount,
+ getSelectedToken,
+ conversionRateSelector,
+ transactionsSelector,
+}
+
+module.exports = selectors
+
+function getSelectedAddress (state) {
+ // TODO: accounts is not defined. Is it needed?
+ const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
+
+ return selectedAddress
+}
+
+function getSelectedIdentity (state) {
+ const selectedAddress = getSelectedAddress(state)
+ const identities = state.metamask.identities
+
+ return identities[selectedAddress]
+}
+
+function getSelectedAccount (state) {
+ const accounts = state.metamask.accounts
+ const selectedAddress = getSelectedAddress(state)
+
+ return accounts[selectedAddress]
+}
+
+function getSelectedToken (state) {
+ const tokens = state.metamask.tokens || []
+ const selectedTokenAddress = state.metamask.selectedTokenAddress
+ const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
+
+ return selectedToken || null
+}
+
+function conversionRateSelector (state) {
+ return state.metamask.conversionRate
+}
+
+function transactionsSelector (state) {
+ const { network, selectedTokenAddress } = state.metamask
+ const unapprovedMsgs = valuesFor(state.metamask.unapprovedMsgs)
+ const shapeShiftTxList = (network === '1') ? state.metamask.shapeShiftTxList : undefined
+ const transactions = state.metamask.selectedAddressTxList || []
+ const txsToRender = !shapeShiftTxList ? transactions.concat(unapprovedMsgs) : transactions.concat(unapprovedMsgs, shapeShiftTxList)
+
+ console.log({txsToRender, selectedTokenAddress})
+ return selectedTokenAddress
+ ? txsToRender
+ .filter(({ txParams: { to } }) => to === selectedTokenAddress)
+ .sort((a, b) => b.time - a.time)
+ : txsToRender
+ .sort((a, b) => b.time - a.time)
+}
diff --git a/ui/app/send.js b/ui/app/send.js
index e59c1130e..bfc569b7d 100644
--- a/ui/app/send.js
+++ b/ui/app/send.js
@@ -1,42 +1,89 @@
-const inherits = require('util').inherits
+const { inherits } = require('util')
const PersistentForm = require('../lib/persistent-form')
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const Identicon = require('./components/identicon')
-const actions = require('./actions')
-const util = require('./util')
-const numericBalance = require('./util').numericBalance
-const addressSummary = require('./util').addressSummary
-const isHex = require('./util').isHex
-const EthBalance = require('./components/eth-balance')
const EnsInput = require('./components/ens-input')
-const ethUtil = require('ethereumjs-util')
+const GasTooltip = require('./components/send/gas-tooltip')
+const CurrencyToggle = require('./components/send/currency-toggle')
+const GasFeeDisplay = require('./components/send/gas-fee-display')
+const { getSelectedIdentity } = require('./selectors')
+
+const {
+ showAccountsPage,
+ backToAccountDetail,
+ displayWarning,
+ hideWarning,
+ addToAddressBook,
+ signTx,
+} = require('./actions')
+const { stripHexPrefix, addHexPrefix } = require('ethereumjs-util')
+const { isHex, numericBalance } = require('./util')
+const { conversionUtil } = require('./conversion-util')
+const BigNumber = require('bignumber.js')
+
+const ARAGON = '960b236A07cf122663c4303350609A66A7B288C0'
+
module.exports = connect(mapStateToProps)(SendTransactionScreen)
function mapStateToProps (state) {
- var result = {
- address: state.metamask.selectedAddress,
- accounts: state.metamask.accounts,
- identities: state.metamask.identities,
- warning: state.appState.warning,
- network: state.metamask.network,
- addressBook: state.metamask.addressBook,
- conversionRate: state.metamask.conversionRate,
- currentCurrency: state.metamask.currentCurrency,
- }
-
- result.error = result.warning && result.warning.split('.')[0]
-
- result.account = result.accounts[result.address]
- result.identity = result.identities[result.address]
- result.balance = result.account ? numericBalance(result.account.balance) : null
+ const {
+ selectedAddress: address,
+ accounts,
+ identities,
+ network,
+ addressBook,
+ conversionRate,
+ currentBlockGasLimit: blockGasLimit,
+ } = state.metamask
+ const { warning } = state.appState
+ const selectedIdentity = getSelectedIdentity(state)
+ const account = accounts[address]
- return result
+ return {
+ address,
+ accounts,
+ identities,
+ network,
+ addressBook,
+ conversionRate,
+ blockGasLimit,
+ warning,
+ selectedIdentity,
+ error: warning && warning.split('.')[0],
+ account,
+ identity: identities[address],
+ balance: account ? numericBalance(account.balance) : null,
+ }
}
inherits(SendTransactionScreen, PersistentForm)
function SendTransactionScreen () {
PersistentForm.call(this)
+
+ // [WIP] These are the bare minimum of tx props needed to sign a transaction
+ // We will need a few more for contract-related interactions
+ this.state = {
+ newTx: {
+ from: '',
+ to: '',
+ // these values are hardcoded, so "Next" can be clicked
+ amount: '0x0', // see L544
+ gasPrice: '0x5d21dba00',
+ gas: '0x7b0d',
+ txData: null,
+ memo: '',
+ },
+ activeCurrency: 'USD',
+ tooltipIsOpen: false,
+ }
+
+ this.back = this.back.bind(this)
+ this.closeTooltip = this.closeTooltip.bind(this)
+ this.onSubmit = this.onSubmit.bind(this)
+ this.recipientDidChange = this.recipientDidChange.bind(this)
+ this.setActiveCurrency = this.setActiveCurrency.bind(this)
+ this.toggleTooltip = this.toggleTooltip.bind(this)
}
SendTransactionScreen.prototype.render = function () {
@@ -44,195 +91,260 @@ SendTransactionScreen.prototype.render = function () {
const props = this.props
const {
- address,
- account,
- identity,
- network,
- identities,
- addressBook,
+ // selectedIdentity,
+ // network,
+ // identities,
+ // addressBook,
conversionRate,
- currentCurrency,
} = props
+ const { blockGasLimit, newTx, activeCurrency } = this.state
+ const { gas, gasPrice } = newTx
+ // console.log(`activeCurrency`, activeCurrency)
+ // console.log({ selectedIdentity, identities })
+ // console.log('SendTransactionScreen state:', this.state)
+
return (
- h('.send-screen.flex-column.flex-grow', [
-
- //
- // Sender Profile
- //
-
- h('.account-data-subsection.flex-row.flex-grow', {
- style: {
- margin: '0 20px',
- },
- }, [
-
- // header - identicon + nav
- h('.flex-row.flex-space-between', {
- style: {
- marginTop: '15px',
- },
- }, [
- // back button
- h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', {
- onClick: this.back.bind(this),
+ h('div.send-screen-wrapper', [
+ // Main Send token Card
+ h('div.send-screen-card', [
+
+ h('img.send-eth-icon', { src: '../images/eth_logo.svg' }),
+
+ h('div.send-screen__title', 'Send'),
+
+ h('div.send-screen__subtitle', 'Send Ethereum to anyone with an Ethereum account'),
+
+ h('div.send-screen-input-wrapper', [
+
+ h('div', 'From:'),
+
+ h('input.large-input.send-screen-input', {
+ list: 'accounts',
+ placeholder: 'Account',
+ value: this.state.newTx.from,
+ onChange: (event) => {
+ console.log('event', event.target.value)
+ this.setState({
+ newTx: {
+ ...this.state.newTx,
+ from: event.target.value,
+ },
+ })
+ },
}),
- // large identicon
- h('.identicon-wrapper.flex-column.flex-center.select-none', [
- h(Identicon, {
- diameter: 62,
- address: address,
+ h('datalist#accounts', [
+ Object.keys(props.identities).map((key) => {
+ const identity = props.identities[key]
+ return h('option', {
+ value: identity.address,
+ label: identity.name,
+ key: identity.address,
+ })
}),
]),
- // invisible place holder
- h('i.fa.fa-users.fa-lg.invisible', {
- style: {
- marginTop: '28px',
+ ]),
+
+ h('div.send-screen-input-wrapper', [
+
+ h('div', 'To:'),
+
+ h('input.large-input.send-screen-input', {
+ name: 'address',
+ list: 'addresses',
+ placeholder: 'Address',
+ value: this.state.newTx.to,
+ onChange: (event) => {
+ console.log('event', event.target.value)
+ this.setState({
+ newTx: {
+ ...this.state.newTx,
+ to: event.target.value,
+ },
+ })
},
}),
+ h('datalist#addresses', [
+ // Corresponds to the addresses owned.
+ Object.entries(props.identities).map(([key, { address, name }]) => {
+ return h('option', {
+ value: address,
+ label: name,
+ key: address,
+ })
+ }),
+ // Corresponds to previously sent-to addresses.
+ props.addressBook.map(({ address, name }) => {
+ return h('option', {
+ value: address,
+ label: name,
+ key: address,
+ })
+ }),
+ ]),
+
+ // h(EnsInput, {
+ // name: 'address',
+ // placeholder: 'Recipient Address',
+ // value: this.state.newTx.to,
+ // onChange: (event) => {
+ // this.setState({
+ // newTx: Object.assign(
+ // this.state.newTx,
+ // {
+ // to: event.target.value,
+ // }
+ // ),
+ // })
+ // },
+ // network,
+ // identities,
+ // addressBook,
+ // }),
+
]),
- // account label
-
- h('.flex-column', {
- style: {
- marginTop: '10px',
- alignItems: 'flex-start',
- },
- }, [
- h('h2.font-medium.color-forest.flex-center', {
- style: {
- paddingTop: '8px',
- marginBottom: '8px',
- },
- }, identity && identity.name),
+ h('div.send-screen-input-wrapper', [
- // address and getter actions
- h('.flex-row.flex-center', {
- style: {
- marginBottom: '8px',
+ h('div.send-screen-amount-labels', [
+ h('span', 'Amount'),
+ h(CurrencyToggle, {
+ activeCurrency,
+ onClick: (newCurrency) => this.setActiveCurrency(newCurrency),
+ }), // holding on icon from design
+ ]),
+
+ h('input.large-input.send-screen-input', {
+ placeholder: `0 ${activeCurrency}`,
+ type: 'number',
+ onChange: (event) => {
+ this.setState({
+ newTx: Object.assign(
+ this.state.newTx,
+ {
+ amount: event.target.value,
+ }
+ ),
+ })
},
- }, [
+ }),
- h('div', {
- style: {
- lineHeight: '16px',
- },
- }, addressSummary(address)),
+ ]),
- ]),
+ h('div.send-screen-input-wrapper', [
+ this.state.tooltipIsOpen && h(GasTooltip, {
+ className: 'send-tooltip',
+ gasPrice,
+ gasLimit: gas,
+ onClose: this.closeTooltip,
+ onFeeChange: ({gasLimit, gasPrice}) => {
+ this.setState({
+ newTx: {
+ ...this.state.newTx,
+ gas: gasLimit,
+ gasPrice,
+ },
+ })
+ },
+ }),
- // balance
- h('.flex-row.flex-center', [
+ h('div.send-screen-gas-labels', [
+ h('span', [
+ h('i.fa.fa-bolt'),
+ 'Gas fee:',
+ ]),
+ h('span', 'What\'s this?'),
+ ]),
- h(EthBalance, {
- value: account && account.balance,
+ // TODO: handle loading time when switching to USD
+ h('div.large-input.send-screen-gas-input', {}, [
+ h(GasFeeDisplay, {
+ activeCurrency,
conversionRate,
- currentCurrency,
+ gas,
+ gasPrice,
+ blockGasLimit,
}),
-
+ h('div.send-screen-gas-input-customize', {
+ onClick: this.toggleTooltip,
+ }, [
+ 'Customize',
+ ]),
]),
- ]),
- ]),
-
- //
- // Required Fields
- //
-
- h('h3.flex-center.text-transform-uppercase', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- marginTop: '15px',
- marginBottom: '16px',
- },
- }, [
- 'Send Transaction',
- ]),
-
- // error message
- props.error && h('span.error.flex-center', props.error),
-
- // 'to' field
- h('section.flex-row.flex-center', [
- h(EnsInput, {
- name: 'address',
- placeholder: 'Recipient Address',
- onChange: this.recipientDidChange.bind(this),
- network,
- identities,
- addressBook,
- }),
- ]),
- // 'amount' and send button
- h('section.flex-row.flex-center', [
-
- h('input.large-input', {
- name: 'amount',
- placeholder: 'Amount',
- type: 'number',
- style: {
- marginRight: '6px',
- },
- dataset: {
- persistentFormId: 'tx-amount',
- },
- }),
-
- h('button.primary', {
- onClick: this.onSubmit.bind(this),
- style: {
- textTransform: 'uppercase',
- },
- }, 'Next'),
+ ]),
- ]),
+ h('div.send-screen-input-wrapper', [
+ h('div', 'Transaction memo (optional)'),
+ h('input.large-input.send-screen-input', {
+ onChange: () => {
+ this.setState({
+ newTx: Object.assign(
+ this.state.newTx,
+ {
+ memo: event.target.value,
+ }
+ ),
+ })
+ },
+ }),
+ ]),
- //
- // Optional Fields
- //
- h('h3.flex-center.text-transform-uppercase', {
- style: {
- background: '#EBEBEB',
- color: '#AEAEAE',
- marginTop: '16px',
- marginBottom: '16px',
- },
- }, [
- 'Transaction Data (optional)',
+ h('div.send-screen-input-wrapper', {}, [
+ h('div', {}, ['Data (optional)']),
+ h('input.large-input.send-screen-input', {
+ onChange: () => {
+ this.setState({
+ newTx: Object.assign(
+ this.state.newTx,
+ {
+ txData: event.target.value,
+ }
+ ),
+ })
+ },
+ }),
+ ]),
]),
- // 'data' field
+ // Buttons underneath card
h('section.flex-column.flex-center', [
- h('input.large-input', {
- name: 'txData',
- placeholder: '0x01234',
- style: {
- width: '100%',
- resize: 'none',
- },
- dataset: {
- persistentFormId: 'tx-data',
- },
- }),
+ h('button.btn-secondary.send-screen__send-button', {
+ onClick: (event) => this.onSubmit(event),
+ }, 'Next'),
+ h('button.btn-tertiary.send-screen__cancel-button', {
+ onClick: this.back,
+ }, 'Cancel'),
]),
])
+
)
}
+SendTransactionScreen.prototype.toggleTooltip = function () {
+ this.setState({ tooltipIsOpen: !this.state.tooltipIsOpen })
+}
+
+SendTransactionScreen.prototype.closeTooltip = function () {
+ this.setState({ tooltipIsOpen: false })
+}
+
+SendTransactionScreen.prototype.setActiveCurrency = function (newCurrency) {
+ this.setState({ activeCurrency: newCurrency })
+}
+
SendTransactionScreen.prototype.navigateToAccounts = function (event) {
event.stopPropagation()
- this.props.dispatch(actions.showAccountsPage())
+ this.props.dispatch(showAccountsPage())
}
SendTransactionScreen.prototype.back = function () {
var address = this.props.address
- this.props.dispatch(actions.backToAccountDetail(address))
+ this.props.dispatch(backToAccountDetail(address))
}
SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickname) {
@@ -242,25 +354,36 @@ SendTransactionScreen.prototype.recipientDidChange = function (recipient, nickna
})
}
-SendTransactionScreen.prototype.onSubmit = function () {
+SendTransactionScreen.prototype.onSubmit = function (event) {
+ event.preventDefault()
const state = this.state || {}
- const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
+
+ // const recipient = state.recipient || document.querySelector('input[name="address"]').value.replace(/^[.\s]+|[.\s]+$/g, '')
+ const recipient = state.newTx.to
+
const nickname = state.nickname || ' '
- const input = document.querySelector('input[name="amount"]').value
- const value = util.normalizeEthStringToWei(input)
- const txData = document.querySelector('input[name="txData"]').value
- const balance = this.props.balance
+
+ // const input = document.querySelector('input[name="amount"]').value
+ // const input = state.newTx.value
+ // const value = util.normalizeEthStringToWei(input)
+
+ // https://consensys.slack.com/archives/G1L7H42BT/p1503439134000169?thread_ts=1503438076.000411&cid=G1L7H42BT
+ // From @kumavis: "not needed for MVP but we will end up adding it again so consider just adding it now"
+ const txData = false
+ // Must replace with memo data.
+ // const txData = document.querySelector('input[name="txData"]').value
+
let message
- if (value.gt(balance)) {
- message = 'Insufficient funds.'
- return this.props.dispatch(actions.displayWarning(message))
- }
+ // if (value.gt(balance)) {
+ // message = 'Insufficient funds.'
+ // return this.props.dispatch(actions.displayWarning(message))
+ // }
- if (input < 0) {
- message = 'Can not send negative amounts of ETH.'
- return this.props.dispatch(actions.displayWarning(message))
- }
+ // if (input < 0) {
+ // message = 'Can not send negative amounts of ETH.'
+ // return this.props.dispatch(actions.displayWarning(message))
+ // }
if ((util.isInvalidChecksumAddress(recipient))) {
message = 'Recipient address checksum is invalid.'
@@ -272,22 +395,42 @@ SendTransactionScreen.prototype.onSubmit = function () {
return this.props.dispatch(actions.displayWarning(message))
}
- if (!isHex(ethUtil.stripHexPrefix(txData)) && txData) {
+ if (txData && !isHex(stripHexPrefix(txData))) {
message = 'Transaction data must be hex string.'
- return this.props.dispatch(actions.displayWarning(message))
+ return this.props.dispatch(displayWarning(message))
}
- this.props.dispatch(actions.hideWarning())
+ this.props.dispatch(hideWarning())
+
+ this.props.dispatch(addToAddressBook(recipient, nickname))
- this.props.dispatch(actions.addToAddressBook(recipient, nickname))
+ // TODO: need a clean way to integrate this into conversionUtil
+ const sendConversionRate = state.activeCurrency === 'ETH'
+ ? this.props.conversionRate
+ : new BigNumber(1.0).div(this.props.conversionRate)
+ const sendAmount = conversionUtil(this.state.newTx.amount, {
+ fromNumericBase: 'dec',
+ toNumericBase: 'hex',
+ fromCurrency: state.activeCurrency,
+ toCurrency: 'ETH',
+ toDenomination: 'WEI',
+ conversionRate: sendConversionRate,
+ })
+
var txParams = {
- from: this.props.address,
- value: '0x' + value.toString(16),
+ from: this.state.newTx.from,
+ to: this.state.newTx.to,
+
+ value: sendAmount,
+
+ // New: gas will now be specified on this step
+ gas: this.state.newTx.gas,
+ gasPrice: this.state.newTx.gasPrice,
}
- if (recipient) txParams.to = ethUtil.addHexPrefix(recipient)
+ if (recipient) txParams.to = addHexPrefix(recipient)
if (txData) txParams.data = txData
- this.props.dispatch(actions.signTx(txParams))
+ this.props.dispatch(signTx(txParams))
}
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index 4180791c4..ec97b03bf 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -50,7 +50,7 @@ UnlockScreen.prototype.render = function () {
id: 'password-box',
placeholder: 'enter password',
style: {
-
+ background: 'white',
},
onKeyPress: this.onKeyPress.bind(this),
onInput: this.inputChanged.bind(this),
diff --git a/ui/app/util.js b/ui/app/util.js
index 3f8b4dcc3..7aace1b3c 100644
--- a/ui/app/util.js
+++ b/ui/app/util.js
@@ -1,4 +1,16 @@
+const abi = require('human-standard-token-abi')
const ethUtil = require('ethereumjs-util')
+const hexToBn = require('../../app/scripts/lib/hex-to-bn')
+const vreme = new (require('vreme'))()
+
+const MIN_GAS_PRICE_GWEI_BN = new ethUtil.BN(1)
+const GWEI_FACTOR = new ethUtil.BN(1e9)
+const MIN_GAS_PRICE_BN = MIN_GAS_PRICE_GWEI_BN.mul(GWEI_FACTOR)
+
+// formatData :: ( date: <Unix Timestamp> ) -> String
+function formatDate (date) {
+ return vreme.format(new Date(date), 'March 16 2014 14:30')
+}
var valueTable = {
wei: '1000000000000000000',
@@ -36,6 +48,11 @@ module.exports = {
valueTable: valueTable,
bnTable: bnTable,
isHex: isHex,
+ formatDate,
+ bnMultiplyByFraction,
+ getTxFeeBn,
+ shortenBalance,
+ getContractAtAddress,
exportAsFile: exportAsFile,
isInvalidChecksumAddress,
}
@@ -224,6 +241,24 @@ function isHex (str) {
return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/))
}
+function bnMultiplyByFraction (targetBN, numerator, denominator) {
+ const numBN = new ethUtil.BN(numerator)
+ const denomBN = new ethUtil.BN(denominator)
+ return targetBN.mul(numBN).div(denomBN)
+}
+
+function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimit) {
+ const gasBn = hexToBn(gas)
+ const gasPriceBn = hexToBn(gasPrice)
+ const txFeeBn = gasBn.mul(gasPriceBn)
+
+ return txFeeBn.toString(16)
+}
+
+function getContractAtAddress (tokenAddress) {
+ return global.eth.contract(abi).at(tokenAddress)
+}
+
function exportAsFile (filename, data) {
// source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
const blob = new Blob([data], {type: 'text/csv'})