feature/default_network_editable
commit
8ee01f4e99
@ -1,4 +1,4 @@ |
|||||||
{ |
{ |
||||||
"presets": [["env"], "react", "stage-0"], |
"presets": [["env", { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } } ], "react", "stage-0"], |
||||||
"plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"] |
"plugins": ["transform-runtime", "transform-async-to-generator", "transform-class-properties"] |
||||||
} |
} |
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,202 @@ |
|||||||
|
const inherits = require('util').inherits |
||||||
|
const Component = require('react').Component |
||||||
|
const h = require('react-hyperscript') |
||||||
|
const connect = require('react-redux').connect |
||||||
|
const actions = require('../../ui/app/actions') |
||||||
|
const Tooltip = require('./components/tooltip.js') |
||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
const Copyable = require('./components/copyable') |
||||||
|
const addressSummary = require('./util').addressSummary |
||||||
|
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps)(AddSuggestedTokenScreen) |
||||||
|
|
||||||
|
function mapStateToProps (state) { |
||||||
|
return { |
||||||
|
identities: state.metamask.identities, |
||||||
|
suggestedTokens: state.metamask.suggestedTokens, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inherits(AddSuggestedTokenScreen, Component) |
||||||
|
function AddSuggestedTokenScreen () { |
||||||
|
this.state = { |
||||||
|
warning: null, |
||||||
|
} |
||||||
|
Component.call(this) |
||||||
|
} |
||||||
|
|
||||||
|
AddSuggestedTokenScreen.prototype.render = function () { |
||||||
|
const state = this.state |
||||||
|
const props = this.props |
||||||
|
const { warning } = state |
||||||
|
const key = Object.keys(props.suggestedTokens)[0] |
||||||
|
const { address, symbol, decimals } = props.suggestedTokens[key] |
||||||
|
|
||||||
|
return ( |
||||||
|
h('.flex-column.flex-grow', [ |
||||||
|
|
||||||
|
// subtitle and nav
|
||||||
|
h('.section-title.flex-row.flex-center', [ |
||||||
|
h('h2.page-subtitle', 'Add Suggested Token'), |
||||||
|
]), |
||||||
|
|
||||||
|
h('.error', { |
||||||
|
style: { |
||||||
|
display: warning ? 'block' : 'none', |
||||||
|
padding: '0 20px', |
||||||
|
textAlign: 'center', |
||||||
|
}, |
||||||
|
}, warning), |
||||||
|
|
||||||
|
// conf view
|
||||||
|
h('.flex-column.flex-justify-center.flex-grow.select-none', [ |
||||||
|
h('.flex-space-around', { |
||||||
|
style: { |
||||||
|
padding: '20px', |
||||||
|
}, |
||||||
|
}, [ |
||||||
|
|
||||||
|
h('div', [ |
||||||
|
h(Tooltip, { |
||||||
|
position: 'top', |
||||||
|
title: 'The contract of the actual token contract. Click for more info.', |
||||||
|
}, [ |
||||||
|
h('a', { |
||||||
|
style: { fontWeight: 'bold', paddingRight: '10px'}, |
||||||
|
href: 'https://support.metamask.io/kb/article/24-what-is-a-token-contract-address', |
||||||
|
target: '_blank', |
||||||
|
}, [ |
||||||
|
h('span', 'Token Contract Address '), |
||||||
|
h('i.fa.fa-question-circle'), |
||||||
|
]), |
||||||
|
]), |
||||||
|
]), |
||||||
|
|
||||||
|
h('div', { |
||||||
|
style: { display: 'flex' }, |
||||||
|
}, [ |
||||||
|
h(Copyable, { |
||||||
|
value: ethUtil.toChecksumAddress(address), |
||||||
|
}, [ |
||||||
|
h('span#token-address', { |
||||||
|
style: { |
||||||
|
width: 'inherit', |
||||||
|
flex: '1 0 auto', |
||||||
|
height: '30px', |
||||||
|
margin: '8px', |
||||||
|
display: 'flex', |
||||||
|
}, |
||||||
|
}, addressSummary(address, 24, 4, false)), |
||||||
|
]), |
||||||
|
]), |
||||||
|
|
||||||
|
h('div', [ |
||||||
|
h('span', { |
||||||
|
style: { fontWeight: 'bold', paddingRight: '10px'}, |
||||||
|
}, 'Token Symbol'), |
||||||
|
]), |
||||||
|
|
||||||
|
h('div', { style: {display: 'flex'} }, [ |
||||||
|
h('p#token_symbol', { |
||||||
|
style: { |
||||||
|
width: 'inherit', |
||||||
|
flex: '1 0 auto', |
||||||
|
height: '30px', |
||||||
|
margin: '8px', |
||||||
|
}, |
||||||
|
}, symbol), |
||||||
|
]), |
||||||
|
|
||||||
|
h('div', [ |
||||||
|
h('span', { |
||||||
|
style: { fontWeight: 'bold', paddingRight: '10px'}, |
||||||
|
}, 'Decimals of Precision'), |
||||||
|
]), |
||||||
|
|
||||||
|
h('div', { style: {display: 'flex'} }, [ |
||||||
|
h('p#token_decimals', { |
||||||
|
type: 'number', |
||||||
|
style: { |
||||||
|
width: 'inherit', |
||||||
|
flex: '1 0 auto', |
||||||
|
height: '30px', |
||||||
|
margin: '8px', |
||||||
|
}, |
||||||
|
}, decimals), |
||||||
|
]), |
||||||
|
|
||||||
|
h('button', { |
||||||
|
style: { |
||||||
|
alignSelf: 'center', |
||||||
|
margin: '8px', |
||||||
|
}, |
||||||
|
onClick: (event) => { |
||||||
|
this.props.dispatch(actions.removeSuggestedTokens()) |
||||||
|
}, |
||||||
|
}, 'Cancel'), |
||||||
|
|
||||||
|
h('button', { |
||||||
|
style: { |
||||||
|
alignSelf: 'center', |
||||||
|
margin: '8px', |
||||||
|
}, |
||||||
|
onClick: (event) => { |
||||||
|
const valid = this.validateInputs({ address, symbol, decimals }) |
||||||
|
if (!valid) return |
||||||
|
|
||||||
|
this.props.dispatch(actions.addToken(address.trim(), symbol.trim(), decimals)) |
||||||
|
.then(() => { |
||||||
|
this.props.dispatch(actions.removeSuggestedTokens()) |
||||||
|
}) |
||||||
|
}, |
||||||
|
}, 'Add'), |
||||||
|
]), |
||||||
|
]), |
||||||
|
]) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
AddSuggestedTokenScreen.prototype.componentWillMount = function () { |
||||||
|
if (typeof global.ethereumProvider === 'undefined') return |
||||||
|
} |
||||||
|
|
||||||
|
AddSuggestedTokenScreen.prototype.validateInputs = function (opts) { |
||||||
|
let msg = '' |
||||||
|
const identitiesList = Object.keys(this.props.identities) |
||||||
|
const { address, symbol, decimals } = opts |
||||||
|
const standardAddress = ethUtil.addHexPrefix(address).toLowerCase() |
||||||
|
|
||||||
|
const validAddress = ethUtil.isValidAddress(address) |
||||||
|
if (!validAddress) { |
||||||
|
msg += 'Address is invalid.' |
||||||
|
} |
||||||
|
|
||||||
|
const validDecimals = decimals >= 0 && decimals <= 36 |
||||||
|
if (!validDecimals) { |
||||||
|
msg += 'Decimals must be at least 0, and not over 36. ' |
||||||
|
} |
||||||
|
|
||||||
|
const symbolLen = symbol.trim().length |
||||||
|
const validSymbol = symbolLen > 0 && symbolLen < 10 |
||||||
|
if (!validSymbol) { |
||||||
|
msg += 'Symbol must be between 0 and 10 characters.' |
||||||
|
} |
||||||
|
|
||||||
|
const ownAddress = identitiesList.includes(standardAddress) |
||||||
|
if (ownAddress) { |
||||||
|
msg = 'Personal address detected. Input the token contract address.' |
||||||
|
} |
||||||
|
|
||||||
|
const isValid = validAddress && validDecimals && !ownAddress |
||||||
|
|
||||||
|
if (!isValid) { |
||||||
|
this.setState({ |
||||||
|
warning: msg, |
||||||
|
}) |
||||||
|
} else { |
||||||
|
this.setState({ warning: null }) |
||||||
|
} |
||||||
|
|
||||||
|
return isValid |
||||||
|
} |
@ -1,33 +0,0 @@ |
|||||||
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.account-and-transaction-details', [ |
|
||||||
// wallet
|
|
||||||
h(WalletView, { |
|
||||||
style: { |
|
||||||
}, |
|
||||||
responsiveDisplayClassname: '.lap-visible', |
|
||||||
}, [ |
|
||||||
]), |
|
||||||
|
|
||||||
// transaction
|
|
||||||
h(TxView, { |
|
||||||
style: { |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
]), |
|
||||||
]) |
|
||||||
} |
|
||||||
|
|
@ -1,267 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
const connect = require('react-redux').connect |
|
||||||
const actions = require('../actions') |
|
||||||
const CoinbaseForm = require('./coinbase-form') |
|
||||||
const ShapeshiftForm = require('./shapeshift-form') |
|
||||||
const Loading = require('./loading-screen') |
|
||||||
const AccountPanel = require('./account-panel') |
|
||||||
const RadioList = require('./custom-radio-list') |
|
||||||
const { getNetworkDisplayName } = require('../../../app/scripts/controllers/network/util') |
|
||||||
|
|
||||||
BuyButtonSubview.contextTypes = { |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(BuyButtonSubview) |
|
||||||
|
|
||||||
|
|
||||||
function mapStateToProps (state) { |
|
||||||
return { |
|
||||||
identity: state.appState.identity, |
|
||||||
account: state.metamask.accounts[state.appState.buyView.buyAddress], |
|
||||||
warning: state.appState.warning, |
|
||||||
buyView: state.appState.buyView, |
|
||||||
network: state.metamask.network, |
|
||||||
provider: state.metamask.provider, |
|
||||||
context: state.appState.currentView.context, |
|
||||||
isSubLoading: state.appState.isSubLoading, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
inherits(BuyButtonSubview, Component) |
|
||||||
function BuyButtonSubview () { |
|
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.render = function () { |
|
||||||
return ( |
|
||||||
h('div', { |
|
||||||
style: { |
|
||||||
width: '100%', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
this.headerSubview(), |
|
||||||
this.primarySubview(), |
|
||||||
]) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.headerSubview = function () { |
|
||||||
const props = this.props |
|
||||||
const isLoading = props.isSubLoading |
|
||||||
return ( |
|
||||||
|
|
||||||
h('.flex-column', { |
|
||||||
style: { |
|
||||||
alignItems: 'center', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
|
|
||||||
// header bar (back button, label)
|
|
||||||
h('.flex-row', { |
|
||||||
style: { |
|
||||||
alignItems: 'center', |
|
||||||
justifyContent: 'center', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { |
|
||||||
onClick: this.backButtonContext.bind(this), |
|
||||||
style: { |
|
||||||
position: 'absolute', |
|
||||||
left: '10px', |
|
||||||
}, |
|
||||||
}), |
|
||||||
h('h2.text-transform-uppercase.flex-center', { |
|
||||||
style: { |
|
||||||
width: '100vw', |
|
||||||
background: 'rgb(235, 235, 235)', |
|
||||||
color: 'rgb(174, 174, 174)', |
|
||||||
paddingTop: '4px', |
|
||||||
paddingBottom: '4px', |
|
||||||
}, |
|
||||||
}, this.context.t('depositEth')), |
|
||||||
]), |
|
||||||
|
|
||||||
// loading indication
|
|
||||||
h('div', { |
|
||||||
style: { |
|
||||||
position: 'absolute', |
|
||||||
top: '57vh', |
|
||||||
left: '49vw', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
isLoading && h(Loading), |
|
||||||
]), |
|
||||||
|
|
||||||
// account panel
|
|
||||||
h('div', { |
|
||||||
style: { |
|
||||||
width: '80%', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h(AccountPanel, { |
|
||||||
showFullAddress: true, |
|
||||||
identity: props.identity, |
|
||||||
account: props.account, |
|
||||||
}), |
|
||||||
]), |
|
||||||
|
|
||||||
h('.flex-row', { |
|
||||||
style: { |
|
||||||
alignItems: 'center', |
|
||||||
justifyContent: 'center', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h('h3.text-transform-uppercase.flex-center', { |
|
||||||
style: { |
|
||||||
paddingLeft: '15px', |
|
||||||
width: '100vw', |
|
||||||
background: 'rgb(235, 235, 235)', |
|
||||||
color: 'rgb(174, 174, 174)', |
|
||||||
paddingTop: '4px', |
|
||||||
paddingBottom: '4px', |
|
||||||
}, |
|
||||||
}, this.context.t('selectService')), |
|
||||||
]), |
|
||||||
|
|
||||||
]) |
|
||||||
|
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
BuyButtonSubview.prototype.primarySubview = function () { |
|
||||||
const props = this.props |
|
||||||
const network = props.network |
|
||||||
|
|
||||||
switch (network) { |
|
||||||
case 'loading': |
|
||||||
return |
|
||||||
|
|
||||||
case '1': |
|
||||||
return this.mainnetSubview() |
|
||||||
|
|
||||||
// Ropsten, Rinkeby, Kovan
|
|
||||||
case '3': |
|
||||||
case '4': |
|
||||||
case '42': |
|
||||||
const networkName = getNetworkDisplayName(network) |
|
||||||
const label = `${networkName} ${this.context.t('testFaucet')}` |
|
||||||
return ( |
|
||||||
h('div.flex-column', { |
|
||||||
style: { |
|
||||||
alignItems: 'center', |
|
||||||
margin: '20px 50px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h('button.text-transform-uppercase', { |
|
||||||
onClick: () => this.props.dispatch(actions.buyEth({ network })), |
|
||||||
style: { |
|
||||||
marginTop: '15px', |
|
||||||
}, |
|
||||||
}, label), |
|
||||||
// Kovan only: Dharma loans beta
|
|
||||||
network === '42' ? ( |
|
||||||
h('button.text-transform-uppercase', { |
|
||||||
onClick: () => this.navigateTo('https://borrow.dharma.io/'), |
|
||||||
style: { |
|
||||||
marginTop: '15px', |
|
||||||
}, |
|
||||||
}, this.context.t('borrowDharma')) |
|
||||||
) : null, |
|
||||||
]) |
|
||||||
) |
|
||||||
|
|
||||||
default: |
|
||||||
return ( |
|
||||||
h('h2.error', this.context.t('unknownNetworkId')) |
|
||||||
) |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.mainnetSubview = function () { |
|
||||||
const props = this.props |
|
||||||
|
|
||||||
return ( |
|
||||||
|
|
||||||
h('.flex-column', { |
|
||||||
style: { |
|
||||||
alignItems: 'center', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
|
|
||||||
h('.flex-row.selected-exchange', { |
|
||||||
style: { |
|
||||||
position: 'relative', |
|
||||||
right: '35px', |
|
||||||
marginTop: '20px', |
|
||||||
marginBottom: '20px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h(RadioList, { |
|
||||||
defaultFocus: props.buyView.subview, |
|
||||||
labels: [ |
|
||||||
'Coinbase', |
|
||||||
'ShapeShift', |
|
||||||
], |
|
||||||
subtext: { |
|
||||||
'Coinbase': `${this.context.t('crypto')}/${this.context.t('fiat')} (${this.context.t('usaOnly')})`, |
|
||||||
'ShapeShift': this.context.t('crypto'), |
|
||||||
}, |
|
||||||
onClick: this.radioHandler.bind(this), |
|
||||||
}), |
|
||||||
]), |
|
||||||
|
|
||||||
h('h3.text-transform-uppercase', { |
|
||||||
style: { |
|
||||||
paddingLeft: '15px', |
|
||||||
fontFamily: 'Montserrat Light', |
|
||||||
width: '100vw', |
|
||||||
background: 'rgb(235, 235, 235)', |
|
||||||
color: 'rgb(174, 174, 174)', |
|
||||||
paddingTop: '4px', |
|
||||||
paddingBottom: '4px', |
|
||||||
}, |
|
||||||
}, props.buyView.subview), |
|
||||||
|
|
||||||
this.formVersionSubview(), |
|
||||||
]) |
|
||||||
|
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.formVersionSubview = function () { |
|
||||||
const network = this.props.network |
|
||||||
if (network === '1') { |
|
||||||
if (this.props.buyView.formView.coinbase) { |
|
||||||
return h(CoinbaseForm, this.props) |
|
||||||
} else if (this.props.buyView.formView.shapeshift) { |
|
||||||
return h(ShapeshiftForm, this.props) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.navigateTo = function (url) { |
|
||||||
global.platform.openWindow({ url }) |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.backButtonContext = function () { |
|
||||||
if (this.props.context === 'confTx') { |
|
||||||
this.props.dispatch(actions.showConfTxPage({transForward: false})) |
|
||||||
} else { |
|
||||||
this.props.dispatch(actions.goHome()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
BuyButtonSubview.prototype.radioHandler = function (event) { |
|
||||||
switch (event.target.title) { |
|
||||||
case 'Coinbase': |
|
||||||
return this.props.dispatch(actions.coinBaseSubview()) |
|
||||||
case 'ShapeShift': |
|
||||||
return this.props.dispatch(actions.shapeShiftSubview(this.props.provider.type)) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,26 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import { ETH } from '../../constants/common' |
||||||
|
|
||||||
|
export default class CurrencyDisplay extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
className: PropTypes.string, |
||||||
|
displayValue: PropTypes.string, |
||||||
|
prefix: PropTypes.string, |
||||||
|
currency: PropTypes.oneOf([ETH]), |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { className, displayValue, prefix } = this.props |
||||||
|
const text = `${prefix || ''}${displayValue}` |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={className} |
||||||
|
title={text} |
||||||
|
> |
||||||
|
{ text } |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import CurrencyDisplay from './currency-display.component' |
||||||
|
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util' |
||||||
|
|
||||||
|
const mapStateToProps = (state, ownProps) => { |
||||||
|
const { value, numberOfDecimals = 2, currency } = ownProps |
||||||
|
const { metamask: { currentCurrency, conversionRate } } = state |
||||||
|
|
||||||
|
const toCurrency = currency || currentCurrency |
||||||
|
const convertedValue = getValueFromWeiHex({ value, toCurrency, conversionRate, numberOfDecimals }) |
||||||
|
const formattedValue = formatCurrency(convertedValue, toCurrency) |
||||||
|
const displayValue = `${formattedValue} ${toCurrency.toUpperCase()}` |
||||||
|
|
||||||
|
return { |
||||||
|
displayValue, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(CurrencyDisplay) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './currency-display.container' |
@ -0,0 +1,27 @@ |
|||||||
|
import React from 'react' |
||||||
|
import assert from 'assert' |
||||||
|
import { shallow } from 'enzyme' |
||||||
|
import CurrencyDisplay from '../currency-display.component' |
||||||
|
|
||||||
|
describe('CurrencyDisplay Component', () => { |
||||||
|
it('should render text with a className', () => { |
||||||
|
const wrapper = shallow(<CurrencyDisplay |
||||||
|
displayValue="$123.45" |
||||||
|
className="currency-display" |
||||||
|
/>) |
||||||
|
|
||||||
|
assert.ok(wrapper.hasClass('currency-display')) |
||||||
|
assert.equal(wrapper.text(), '$123.45') |
||||||
|
}) |
||||||
|
|
||||||
|
it('should render text with a prefix', () => { |
||||||
|
const wrapper = shallow(<CurrencyDisplay |
||||||
|
displayValue="$123.45" |
||||||
|
className="currency-display" |
||||||
|
prefix="-" |
||||||
|
/>) |
||||||
|
|
||||||
|
assert.ok(wrapper.hasClass('currency-display')) |
||||||
|
assert.equal(wrapper.text(), '-$123.45') |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,61 @@ |
|||||||
|
import assert from 'assert' |
||||||
|
import proxyquire from 'proxyquire' |
||||||
|
|
||||||
|
let mapStateToProps |
||||||
|
|
||||||
|
proxyquire('../currency-display.container.js', { |
||||||
|
'react-redux': { |
||||||
|
connect: ms => { |
||||||
|
mapStateToProps = ms |
||||||
|
return () => ({}) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
describe('CurrencyDisplay container', () => { |
||||||
|
describe('mapStateToProps()', () => { |
||||||
|
it('should return the correct props', () => { |
||||||
|
const mockState = { |
||||||
|
metamask: { |
||||||
|
conversionRate: 280.45, |
||||||
|
currentCurrency: 'usd', |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const tests = [ |
||||||
|
{ |
||||||
|
props: { |
||||||
|
value: '0x2386f26fc10000', |
||||||
|
numberOfDecimals: 2, |
||||||
|
currency: 'usd', |
||||||
|
}, |
||||||
|
result: { |
||||||
|
displayValue: '$2.80 USD', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
props: { |
||||||
|
value: '0x2386f26fc10000', |
||||||
|
}, |
||||||
|
result: { |
||||||
|
displayValue: '$2.80 USD', |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
props: { |
||||||
|
value: '0x1193461d01595930', |
||||||
|
currency: 'ETH', |
||||||
|
numberOfDecimals: 3, |
||||||
|
}, |
||||||
|
result: { |
||||||
|
displayValue: '1.266 ETH', |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
tests.forEach(({ props, result }) => { |
||||||
|
assert.deepEqual(mapStateToProps(mockState, props), result) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -1,60 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
|
|
||||||
module.exports = RadioList |
|
||||||
|
|
||||||
inherits(RadioList, Component) |
|
||||||
function RadioList () { |
|
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
RadioList.prototype.render = function () { |
|
||||||
const props = this.props |
|
||||||
const activeClass = '.custom-radio-selected' |
|
||||||
const inactiveClass = '.custom-radio-inactive' |
|
||||||
const { |
|
||||||
labels, |
|
||||||
defaultFocus, |
|
||||||
} = props |
|
||||||
|
|
||||||
|
|
||||||
return ( |
|
||||||
h('.flex-row', { |
|
||||||
style: { |
|
||||||
fontSize: '12px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h('.flex-column.custom-radios', { |
|
||||||
style: { |
|
||||||
marginRight: '5px', |
|
||||||
}, |
|
||||||
}, |
|
||||||
labels.map((lable, i) => { |
|
||||||
let isSelcted = (this.state !== null) |
|
||||||
isSelcted = isSelcted ? (this.state.selected === lable) : (defaultFocus === lable) |
|
||||||
return h(isSelcted ? activeClass : inactiveClass, { |
|
||||||
title: lable, |
|
||||||
onClick: (event) => { |
|
||||||
this.setState({selected: event.target.title}) |
|
||||||
props.onClick(event) |
|
||||||
}, |
|
||||||
}) |
|
||||||
}) |
|
||||||
), |
|
||||||
h('.text', {}, |
|
||||||
labels.map((lable) => { |
|
||||||
if (props.subtext) { |
|
||||||
return h('.flex-row', {}, [ |
|
||||||
h('.radio-titles', lable), |
|
||||||
h('.radio-titles-subtext', `- ${props.subtext[lable]}`), |
|
||||||
]) |
|
||||||
} else { |
|
||||||
return h('.radio-titles', lable) |
|
||||||
} |
|
||||||
}) |
|
||||||
), |
|
||||||
]) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
@ -1,23 +1,39 @@ |
|||||||
|
@import './app-header/index'; |
||||||
|
|
||||||
@import './button-group/index'; |
@import './button-group/index'; |
||||||
|
|
||||||
@import './export-text-container/index'; |
@import './confirm-page-container/index'; |
||||||
|
|
||||||
@import './selected-account/index'; |
@import './export-text-container/index'; |
||||||
|
|
||||||
@import './info-box/index'; |
@import './info-box/index'; |
||||||
|
|
||||||
@import './network-display/index'; |
@import './menu-bar/index'; |
||||||
|
|
||||||
@import './confirm-page-container/index'; |
@import './modals/index'; |
||||||
|
|
||||||
|
@import './network-display/index'; |
||||||
|
|
||||||
@import './page-container/index'; |
@import './page-container/index'; |
||||||
|
|
||||||
@import './pages/index'; |
@import './pages/index'; |
||||||
|
|
||||||
@import './modals/index'; |
@import './selected-account/index'; |
||||||
|
|
||||||
@import './sender-to-recipient/index'; |
@import './sender-to-recipient/index'; |
||||||
|
|
||||||
@import './tabs/index'; |
@import './tabs/index'; |
||||||
|
|
||||||
|
@import './transaction-view/index'; |
||||||
|
|
||||||
|
@import './transaction-view-balance/index'; |
||||||
|
|
||||||
|
@import './transaction-list/index'; |
||||||
|
|
||||||
|
@import './transaction-list-item/index'; |
||||||
|
|
||||||
|
@import './transaction-status/index'; |
||||||
|
|
||||||
@import './app-header/index'; |
@import './app-header/index'; |
||||||
|
|
||||||
|
@import './sidebars/index'; |
||||||
|
@ -0,0 +1 @@ |
|||||||
|
export { default } from './menu-bar.container' |
@ -0,0 +1,23 @@ |
|||||||
|
.menu-bar { |
||||||
|
display: flex; |
||||||
|
flex-direction: row; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
flex: 0 0 auto; |
||||||
|
margin-bottom: 16px; |
||||||
|
padding: 5px; |
||||||
|
border-bottom: 1px solid #e5e5e5; |
||||||
|
|
||||||
|
&__sidebar-button { |
||||||
|
font-size: 1.25rem; |
||||||
|
cursor: pointer; |
||||||
|
padding: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
&__open-in-browser { |
||||||
|
cursor: pointer; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
padding: 10px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Tooltip from '../tooltip' |
||||||
|
import SelectedAccount from '../selected-account' |
||||||
|
|
||||||
|
export default class MenuBar extends PureComponent { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
hideSidebar: PropTypes.func, |
||||||
|
isMascara: PropTypes.bool, |
||||||
|
sidebarOpen: PropTypes.bool, |
||||||
|
showSidebar: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { t } = this.context |
||||||
|
const { isMascara, sidebarOpen, hideSidebar, showSidebar } = this.props |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="menu-bar"> |
||||||
|
<Tooltip |
||||||
|
title={t('menu')} |
||||||
|
position="bottom" |
||||||
|
> |
||||||
|
<div |
||||||
|
className="fa fa-bars menu-bar__sidebar-button" |
||||||
|
onClick={() => sidebarOpen ? hideSidebar() : showSidebar()} |
||||||
|
/> |
||||||
|
</Tooltip> |
||||||
|
<SelectedAccount /> |
||||||
|
{ |
||||||
|
!isMascara && ( |
||||||
|
<Tooltip |
||||||
|
title={t('openInTab')} |
||||||
|
position="bottom" |
||||||
|
> |
||||||
|
<div |
||||||
|
className="menu-bar__open-in-browser" |
||||||
|
onClick={() => global.platform.openExtensionInBrowser()} |
||||||
|
> |
||||||
|
<img src="images/popout.svg" /> |
||||||
|
</div> |
||||||
|
</Tooltip> |
||||||
|
) |
||||||
|
} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import MenuBar from './menu-bar.component' |
||||||
|
import { showSidebar, hideSidebar } from '../../actions' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { appState: { sidebar: { isOpen }, isMascara } } = state |
||||||
|
|
||||||
|
return { |
||||||
|
sidebarOpen: isOpen, |
||||||
|
isMascara, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
showSidebar: () => { |
||||||
|
dispatch(showSidebar({ |
||||||
|
transitionName: 'sidebar-right', |
||||||
|
type: 'wallet-view', |
||||||
|
})) |
||||||
|
}, |
||||||
|
hideSidebar: () => dispatch(hideSidebar()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(MenuBar) |
@ -0,0 +1,126 @@ |
|||||||
|
import React, { Component } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import { DEFAULT_ROUTE } from '../../../routes' |
||||||
|
import Button from '../../button' |
||||||
|
import Identicon from '../../../components/identicon' |
||||||
|
import TokenBalance from '../../token-balance' |
||||||
|
|
||||||
|
export default class ConfirmAddSuggestedToken extends Component { |
||||||
|
static contextTypes = { |
||||||
|
t: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object, |
||||||
|
clearPendingTokens: PropTypes.func, |
||||||
|
addToken: PropTypes.func, |
||||||
|
pendingTokens: PropTypes.object, |
||||||
|
removeSuggestedTokens: PropTypes.func, |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount () { |
||||||
|
const { pendingTokens = {}, history } = this.props |
||||||
|
|
||||||
|
if (Object.keys(pendingTokens).length === 0) { |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getTokenName (name, symbol) { |
||||||
|
return typeof name === 'undefined' |
||||||
|
? symbol |
||||||
|
: `${name} (${symbol})` |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { addToken, pendingTokens, removeSuggestedTokens, history } = this.props |
||||||
|
const pendingTokenKey = Object.keys(pendingTokens)[0] |
||||||
|
const pendingToken = pendingTokens[pendingTokenKey] |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="page-container"> |
||||||
|
<div className="page-container__header"> |
||||||
|
<div className="page-container__title"> |
||||||
|
{ this.context.t('addSuggestedTokens') } |
||||||
|
</div> |
||||||
|
<div className="page-container__subtitle"> |
||||||
|
{ this.context.t('likeToAddTokens') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="page-container__content"> |
||||||
|
<div className="confirm-add-token"> |
||||||
|
<div className="confirm-add-token__header"> |
||||||
|
<div className="confirm-add-token__token"> |
||||||
|
{ this.context.t('token') } |
||||||
|
</div> |
||||||
|
<div className="confirm-add-token__balance"> |
||||||
|
{ this.context.t('balance') } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="confirm-add-token__token-list"> |
||||||
|
{ |
||||||
|
Object.entries(pendingTokens) |
||||||
|
.map(([ address, token ]) => { |
||||||
|
const { name, symbol, image } = token |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
className="confirm-add-token__token-list-item" |
||||||
|
key={address} |
||||||
|
> |
||||||
|
<div className="confirm-add-token__token confirm-add-token__data"> |
||||||
|
<Identicon |
||||||
|
className="confirm-add-token__token-icon" |
||||||
|
diameter={48} |
||||||
|
address={address} |
||||||
|
image={image} |
||||||
|
/> |
||||||
|
<div className="confirm-add-token__name"> |
||||||
|
{ this.getTokenName(name, symbol) } |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="confirm-add-token__balance"> |
||||||
|
<TokenBalance token={token} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div className="page-container__footer"> |
||||||
|
<Button |
||||||
|
type="default" |
||||||
|
large |
||||||
|
className="page-container__footer-button" |
||||||
|
onClick={() => { |
||||||
|
removeSuggestedTokens() |
||||||
|
.then(() => { |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
}) |
||||||
|
}} |
||||||
|
> |
||||||
|
{ this.context.t('cancel') } |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
type="primary" |
||||||
|
large |
||||||
|
className="page-container__footer-button" |
||||||
|
onClick={() => { |
||||||
|
addToken(pendingToken) |
||||||
|
.then(() => { |
||||||
|
removeSuggestedTokens() |
||||||
|
.then(() => { |
||||||
|
history.push(DEFAULT_ROUTE) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}} |
||||||
|
> |
||||||
|
{ this.context.t('addToken') } |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import { connect } from 'react-redux' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
|
||||||
|
const extend = require('xtend') |
||||||
|
|
||||||
|
const { addToken, removeSuggestedTokens } = require('../../../actions') |
||||||
|
|
||||||
|
const mapStateToProps = ({ metamask }) => { |
||||||
|
const { pendingTokens, suggestedTokens } = metamask |
||||||
|
const params = extend(pendingTokens, suggestedTokens) |
||||||
|
|
||||||
|
return { |
||||||
|
pendingTokens: params, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => { |
||||||
|
return { |
||||||
|
addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)), |
||||||
|
removeSuggestedTokens: () => dispatch(removeSuggestedTokens()), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps, mapDispatchToProps) |
||||||
|
)(ConfirmAddSuggestedToken) |
@ -0,0 +1,2 @@ |
|||||||
|
import ConfirmAddSuggestedToken from './confirm-add-suggested-token.container' |
||||||
|
module.exports = ConfirmAddSuggestedToken |
@ -1,2 +0,0 @@ |
|||||||
import TokenBalance from './token-balance.container' |
|
||||||
module.exports = TokenBalance |
|
@ -1,16 +0,0 @@ |
|||||||
import React, { Component } from 'react' |
|
||||||
import PropTypes from 'prop-types' |
|
||||||
|
|
||||||
export default class TokenBalance extends Component { |
|
||||||
static propTypes = { |
|
||||||
string: PropTypes.string, |
|
||||||
symbol: PropTypes.string, |
|
||||||
error: PropTypes.string, |
|
||||||
} |
|
||||||
|
|
||||||
render () { |
|
||||||
return ( |
|
||||||
<div className="hide-text-overflow">{ this.props.string }</div> |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -1,3 +0,0 @@ |
|||||||
export const TOKEN_METHOD_TRANSFER = 'transfer' |
|
||||||
export const TOKEN_METHOD_APPROVE = 'approve' |
|
||||||
export const TOKEN_METHOD_TRANSFER_FROM = 'transferfrom' |
|
@ -1,239 +0,0 @@ |
|||||||
const { Component } = require('react') |
|
||||||
const { connect } = require('react-redux') |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const { Redirect, withRouter } = require('react-router-dom') |
|
||||||
const { compose } = require('recompose') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const actions = require('../../actions') |
|
||||||
const log = require('loglevel') |
|
||||||
|
|
||||||
// init
|
|
||||||
const NewKeyChainScreen = require('../../new-keychain') |
|
||||||
// mascara
|
|
||||||
const MascaraBuyEtherScreen = require('../../../../mascara/src/app/first-time/buy-ether-screen').default |
|
||||||
|
|
||||||
// accounts
|
|
||||||
const MainContainer = require('../../main-container') |
|
||||||
|
|
||||||
// other views
|
|
||||||
const BuyView = require('../../components/buy-button-subview') |
|
||||||
const QrView = require('../../components/qr-code') |
|
||||||
|
|
||||||
// Routes
|
|
||||||
const { |
|
||||||
INITIALIZE_BACKUP_PHRASE_ROUTE, |
|
||||||
RESTORE_VAULT_ROUTE, |
|
||||||
CONFIRM_TRANSACTION_ROUTE, |
|
||||||
NOTICE_ROUTE, |
|
||||||
} = require('../../routes') |
|
||||||
|
|
||||||
const { unconfirmedTransactionsCountSelector } = require('../../selectors/confirm-transaction') |
|
||||||
|
|
||||||
class Home extends Component { |
|
||||||
componentDidMount () { |
|
||||||
const { |
|
||||||
history, |
|
||||||
unconfirmedTransactionsCount = 0, |
|
||||||
} = this.props |
|
||||||
|
|
||||||
// unapprovedTxs and unapproved messages
|
|
||||||
if (unconfirmedTransactionsCount > 0) { |
|
||||||
history.push(CONFIRM_TRANSACTION_ROUTE) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
render () { |
|
||||||
log.debug('rendering primary') |
|
||||||
const { |
|
||||||
noActiveNotices, |
|
||||||
lostAccounts, |
|
||||||
forgottenPassword, |
|
||||||
currentView, |
|
||||||
activeAddress, |
|
||||||
seedWords, |
|
||||||
} = this.props |
|
||||||
|
|
||||||
// notices
|
|
||||||
if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { |
|
||||||
return h(Redirect, { |
|
||||||
to: { |
|
||||||
pathname: NOTICE_ROUTE, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// seed words
|
|
||||||
if (seedWords) { |
|
||||||
log.debug('rendering seed words') |
|
||||||
return h(Redirect, { |
|
||||||
to: { |
|
||||||
pathname: INITIALIZE_BACKUP_PHRASE_ROUTE, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
if (forgottenPassword) { |
|
||||||
log.debug('rendering restore vault screen') |
|
||||||
return h(Redirect, { |
|
||||||
to: { |
|
||||||
pathname: RESTORE_VAULT_ROUTE, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// show current view
|
|
||||||
switch (currentView.name) { |
|
||||||
|
|
||||||
case 'accountDetail': |
|
||||||
log.debug('rendering main container') |
|
||||||
return h(MainContainer, {key: 'account-detail'}) |
|
||||||
|
|
||||||
case 'newKeychain': |
|
||||||
log.debug('rendering new keychain screen') |
|
||||||
return h(NewKeyChainScreen, {key: 'new-keychain'}) |
|
||||||
|
|
||||||
case 'buyEth': |
|
||||||
log.debug('rendering buy ether screen') |
|
||||||
return h(BuyView, {key: 'buyEthView'}) |
|
||||||
|
|
||||||
case 'onboardingBuyEth': |
|
||||||
log.debug('rendering onboarding buy ether screen') |
|
||||||
return h(MascaraBuyEtherScreen, {key: 'buyEthView'}) |
|
||||||
|
|
||||||
case 'qr': |
|
||||||
log.debug('rendering show qr screen') |
|
||||||
return h('div', { |
|
||||||
style: { |
|
||||||
position: 'absolute', |
|
||||||
height: '100%', |
|
||||||
top: '0px', |
|
||||||
left: '0px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h('i.fa.fa-arrow-left.fa-lg.cursor-pointer.color-orange', { |
|
||||||
onClick: () => this.props.dispatch(actions.backToAccountDetail(activeAddress)), |
|
||||||
style: { |
|
||||||
marginLeft: '10px', |
|
||||||
marginTop: '50px', |
|
||||||
}, |
|
||||||
}), |
|
||||||
h('div', { |
|
||||||
style: { |
|
||||||
position: 'absolute', |
|
||||||
left: '44px', |
|
||||||
width: '285px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
h(QrView, {key: 'qr'}), |
|
||||||
]), |
|
||||||
]) |
|
||||||
|
|
||||||
default: |
|
||||||
log.debug('rendering default, account detail screen') |
|
||||||
return h(MainContainer, {key: 'account-detail'}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Home.propTypes = { |
|
||||||
currentCurrency: PropTypes.string, |
|
||||||
isLoading: PropTypes.bool, |
|
||||||
loadingMessage: PropTypes.string, |
|
||||||
network: PropTypes.string, |
|
||||||
provider: PropTypes.object, |
|
||||||
frequentRpcList: PropTypes.array, |
|
||||||
currentView: PropTypes.object, |
|
||||||
sidebarOpen: PropTypes.bool, |
|
||||||
isMascara: PropTypes.bool, |
|
||||||
isOnboarding: PropTypes.bool, |
|
||||||
isUnlocked: PropTypes.bool, |
|
||||||
networkDropdownOpen: PropTypes.bool, |
|
||||||
history: PropTypes.object, |
|
||||||
dispatch: PropTypes.func, |
|
||||||
selectedAddress: PropTypes.string, |
|
||||||
noActiveNotices: PropTypes.bool, |
|
||||||
lostAccounts: PropTypes.array, |
|
||||||
isInitialized: PropTypes.bool, |
|
||||||
forgottenPassword: PropTypes.bool, |
|
||||||
activeAddress: PropTypes.string, |
|
||||||
unapprovedTxs: PropTypes.object, |
|
||||||
seedWords: PropTypes.string, |
|
||||||
unapprovedMsgCount: PropTypes.number, |
|
||||||
unapprovedPersonalMsgCount: PropTypes.number, |
|
||||||
unapprovedTypedMessagesCount: PropTypes.number, |
|
||||||
welcomeScreenSeen: PropTypes.bool, |
|
||||||
isPopup: PropTypes.bool, |
|
||||||
isMouseUser: PropTypes.bool, |
|
||||||
t: PropTypes.func, |
|
||||||
unconfirmedTransactionsCount: PropTypes.number, |
|
||||||
} |
|
||||||
|
|
||||||
function mapStateToProps (state) { |
|
||||||
const { appState, metamask } = state |
|
||||||
const { |
|
||||||
networkDropdownOpen, |
|
||||||
sidebarOpen, |
|
||||||
isLoading, |
|
||||||
loadingMessage, |
|
||||||
} = appState |
|
||||||
|
|
||||||
const { |
|
||||||
accounts, |
|
||||||
address, |
|
||||||
isInitialized, |
|
||||||
noActiveNotices, |
|
||||||
seedWords, |
|
||||||
unapprovedTxs, |
|
||||||
nextUnreadNotice, |
|
||||||
lostAccounts, |
|
||||||
unapprovedMsgCount, |
|
||||||
unapprovedPersonalMsgCount, |
|
||||||
unapprovedTypedMessagesCount, |
|
||||||
} = metamask |
|
||||||
const selected = address || Object.keys(accounts)[0] |
|
||||||
|
|
||||||
return { |
|
||||||
// state from plugin
|
|
||||||
networkDropdownOpen, |
|
||||||
sidebarOpen, |
|
||||||
isLoading, |
|
||||||
loadingMessage, |
|
||||||
noActiveNotices, |
|
||||||
isInitialized, |
|
||||||
isUnlocked: state.metamask.isUnlocked, |
|
||||||
selectedAddress: state.metamask.selectedAddress, |
|
||||||
currentView: state.appState.currentView, |
|
||||||
activeAddress: state.appState.activeAddress, |
|
||||||
transForward: state.appState.transForward, |
|
||||||
isMascara: state.metamask.isMascara, |
|
||||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized), |
|
||||||
isPopup: state.metamask.isPopup, |
|
||||||
seedWords: state.metamask.seedWords, |
|
||||||
unapprovedTxs, |
|
||||||
unapprovedMsgs: state.metamask.unapprovedMsgs, |
|
||||||
unapprovedMsgCount, |
|
||||||
unapprovedPersonalMsgCount, |
|
||||||
unapprovedTypedMessagesCount, |
|
||||||
menuOpen: state.appState.menuOpen, |
|
||||||
network: state.metamask.network, |
|
||||||
provider: state.metamask.provider, |
|
||||||
forgottenPassword: state.appState.forgottenPassword, |
|
||||||
nextUnreadNotice, |
|
||||||
lostAccounts, |
|
||||||
frequentRpcList: state.metamask.frequentRpcList || [], |
|
||||||
currentCurrency: state.metamask.currentCurrency, |
|
||||||
isMouseUser: state.appState.isMouseUser, |
|
||||||
isRevealingSeedWords: state.metamask.isRevealingSeedWords, |
|
||||||
Qr: state.appState.Qr, |
|
||||||
welcomeScreenSeen: state.metamask.welcomeScreenSeen, |
|
||||||
|
|
||||||
// state needed to get account dropdown temporarily rendering from app bar
|
|
||||||
selected, |
|
||||||
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = compose( |
|
||||||
withRouter, |
|
||||||
connect(mapStateToProps) |
|
||||||
)(Home) |
|
@ -0,0 +1,77 @@ |
|||||||
|
import React, { PureComponent } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import Media from 'react-media' |
||||||
|
import { Redirect } from 'react-router-dom' |
||||||
|
import WalletView from '../../wallet-view' |
||||||
|
import TransactionView from '../../transaction-view' |
||||||
|
import { |
||||||
|
INITIALIZE_BACKUP_PHRASE_ROUTE, |
||||||
|
RESTORE_VAULT_ROUTE, |
||||||
|
CONFIRM_TRANSACTION_ROUTE, |
||||||
|
NOTICE_ROUTE, |
||||||
|
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, |
||||||
|
} from '../../../routes' |
||||||
|
|
||||||
|
export default class Home extends PureComponent { |
||||||
|
static propTypes = { |
||||||
|
history: PropTypes.object, |
||||||
|
noActiveNotices: PropTypes.bool, |
||||||
|
lostAccounts: PropTypes.array, |
||||||
|
forgottenPassword: PropTypes.bool, |
||||||
|
seedWords: PropTypes.string, |
||||||
|
suggestedTokens: PropTypes.object, |
||||||
|
unconfirmedTransactionsCount: PropTypes.number, |
||||||
|
} |
||||||
|
|
||||||
|
componentDidMount () { |
||||||
|
const { |
||||||
|
history, |
||||||
|
suggestedTokens = {}, |
||||||
|
unconfirmedTransactionsCount = 0, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
// suggested new tokens
|
||||||
|
if (Object.keys(suggestedTokens).length > 0) { |
||||||
|
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE) |
||||||
|
} |
||||||
|
|
||||||
|
if (unconfirmedTransactionsCount > 0) { |
||||||
|
history.push(CONFIRM_TRANSACTION_ROUTE) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
render () { |
||||||
|
const { |
||||||
|
noActiveNotices, |
||||||
|
lostAccounts, |
||||||
|
forgottenPassword, |
||||||
|
seedWords, |
||||||
|
} = this.props |
||||||
|
|
||||||
|
// notices
|
||||||
|
if (!noActiveNotices || (lostAccounts && lostAccounts.length > 0)) { |
||||||
|
return <Redirect to={{ pathname: NOTICE_ROUTE }} /> |
||||||
|
} |
||||||
|
|
||||||
|
// seed words
|
||||||
|
if (seedWords) { |
||||||
|
return <Redirect to={{ pathname: INITIALIZE_BACKUP_PHRASE_ROUTE }}/> |
||||||
|
} |
||||||
|
|
||||||
|
if (forgottenPassword) { |
||||||
|
return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} /> |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="main-container"> |
||||||
|
<div className="account-and-transaction-details"> |
||||||
|
<Media |
||||||
|
query="(min-width: 576px)" |
||||||
|
render={() => <WalletView />} |
||||||
|
/> |
||||||
|
<TransactionView /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
import Home from './home.component' |
||||||
|
import { compose } from 'recompose' |
||||||
|
import { connect } from 'react-redux' |
||||||
|
import { withRouter } from 'react-router-dom' |
||||||
|
import { unconfirmedTransactionsCountSelector } from '../../../selectors/confirm-transaction' |
||||||
|
|
||||||
|
const mapStateToProps = state => { |
||||||
|
const { metamask, appState } = state |
||||||
|
const { |
||||||
|
noActiveNotices, |
||||||
|
lostAccounts, |
||||||
|
seedWords, |
||||||
|
suggestedTokens, |
||||||
|
} = metamask |
||||||
|
const { forgottenPassword } = appState |
||||||
|
|
||||||
|
return { |
||||||
|
noActiveNotices, |
||||||
|
lostAccounts, |
||||||
|
forgottenPassword, |
||||||
|
seedWords, |
||||||
|
suggestedTokens, |
||||||
|
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default compose( |
||||||
|
withRouter, |
||||||
|
connect(mapStateToProps) |
||||||
|
)(Home) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './home.container' |
@ -1,56 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
const connect = require('react-redux').connect |
|
||||||
|
|
||||||
const AccountPanel = require('./account-panel') |
|
||||||
|
|
||||||
PendingMsgDetails.contextTypes = { |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect()(PendingMsgDetails) |
|
||||||
|
|
||||||
|
|
||||||
inherits(PendingMsgDetails, Component) |
|
||||||
function PendingMsgDetails () { |
|
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
PendingMsgDetails.prototype.render = function () { |
|
||||||
var state = this.props |
|
||||||
var msgData = state.txData |
|
||||||
|
|
||||||
var msgParams = msgData.msgParams || {} |
|
||||||
var address = msgParams.from || state.selectedAddress |
|
||||||
var identity = state.identities[address] || { address: address } |
|
||||||
var account = state.accounts[address] || { address: address } |
|
||||||
|
|
||||||
return ( |
|
||||||
h('div', { |
|
||||||
key: msgData.id, |
|
||||||
style: { |
|
||||||
margin: '10px 20px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
|
|
||||||
// account that will sign
|
|
||||||
h(AccountPanel, { |
|
||||||
showFullAddress: true, |
|
||||||
identity: identity, |
|
||||||
account: account, |
|
||||||
imageifyIdenticons: state.imageifyIdenticons, |
|
||||||
}), |
|
||||||
|
|
||||||
// message data
|
|
||||||
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [ |
|
||||||
h('.flex-column.flex-space-between', [ |
|
||||||
h('label.font-small.allcaps', this.context.t('message')), |
|
||||||
h('span.font-small', msgParams.data), |
|
||||||
]), |
|
||||||
]), |
|
||||||
|
|
||||||
]) |
|
||||||
) |
|
||||||
} |
|
@ -1,73 +0,0 @@ |
|||||||
const Component = require('react').Component |
|
||||||
const PropTypes = require('prop-types') |
|
||||||
const h = require('react-hyperscript') |
|
||||||
const inherits = require('util').inherits |
|
||||||
const PendingTxDetails = require('./pending-msg-details') |
|
||||||
const connect = require('react-redux').connect |
|
||||||
|
|
||||||
PendingMsg.contextTypes = { |
|
||||||
t: PropTypes.func, |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = connect()(PendingMsg) |
|
||||||
|
|
||||||
|
|
||||||
inherits(PendingMsg, Component) |
|
||||||
function PendingMsg () { |
|
||||||
Component.call(this) |
|
||||||
} |
|
||||||
|
|
||||||
PendingMsg.prototype.render = function () { |
|
||||||
var state = this.props |
|
||||||
var msgData = state.txData |
|
||||||
|
|
||||||
return ( |
|
||||||
|
|
||||||
h('div', { |
|
||||||
key: msgData.id, |
|
||||||
style: { |
|
||||||
maxWidth: '350px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
|
|
||||||
// header
|
|
||||||
h('h3', { |
|
||||||
style: { |
|
||||||
fontWeight: 'bold', |
|
||||||
textAlign: 'center', |
|
||||||
}, |
|
||||||
}, this.context.t('signMessage')), |
|
||||||
|
|
||||||
h('.error', { |
|
||||||
style: { |
|
||||||
margin: '10px', |
|
||||||
}, |
|
||||||
}, [ |
|
||||||
this.context.t('signNotice'), |
|
||||||
h('a', { |
|
||||||
href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527', |
|
||||||
style: { color: 'rgb(247, 134, 28)' }, |
|
||||||
onClick: (event) => { |
|
||||||
event.preventDefault() |
|
||||||
const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527' |
|
||||||
global.platform.openWindow({ url }) |
|
||||||
}, |
|
||||||
}, this.context.t('readMore')), |
|
||||||
]), |
|
||||||
|
|
||||||
// message details
|
|
||||||
h(PendingTxDetails, state), |
|
||||||
|
|
||||||
// sign + cancel
|
|
||||||
h('.flex-row.flex-space-around', [ |
|
||||||
h('button', { |
|
||||||
onClick: state.cancelMessage, |
|
||||||
}, this.context.t('cancel')), |
|
||||||
h('button', { |
|
||||||
onClick: state.signMessage, |
|
||||||
}, this.context.t('sign')), |
|
||||||
]), |
|
||||||
]) |
|
||||||
|
|
||||||
) |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue