New iteration of cleanup
This commit is contained in:
parent
5b5ec4585d
commit
799baff067
|
@ -1,7 +1,7 @@
|
||||||
const injectCss = require('inject-css')
|
const injectCss = require('inject-css')
|
||||||
const SwController = require('sw-controller')
|
const SwController = require('sw-controller')
|
||||||
const SwStream = require('sw-stream')
|
const SwStream = require('sw-stream')
|
||||||
const MetaMaskUiCss = require('../../ui/css')
|
const MetaMaskUiCss = require('../../old-ui/css')
|
||||||
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
|
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
|
||||||
const startPopup = require('../../app/scripts/popup-core')
|
const startPopup = require('../../app/scripts/popup-core')
|
||||||
|
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
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')
|
|
||||||
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
|
|
||||||
import { PRIMARY, SECONDARY } from '../constants/common'
|
|
||||||
const { getAssetImages, conversionRateSelector, getCurrentCurrency, getMetaMaskAccounts } = require('../selectors')
|
|
||||||
|
|
||||||
const { formatBalance } = require('../util')
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(BalanceComponent)
|
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
|
||||||
const accounts = getMetaMaskAccounts(state)
|
|
||||||
const network = state.metamask.network
|
|
||||||
const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0]
|
|
||||||
const account = accounts[selectedAddress]
|
|
||||||
|
|
||||||
return {
|
|
||||||
account,
|
|
||||||
network,
|
|
||||||
conversionRate: conversionRateSelector(state),
|
|
||||||
currentCurrency: getCurrentCurrency(state),
|
|
||||||
assetImages: getAssetImages(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inherits(BalanceComponent, Component)
|
|
||||||
function BalanceComponent () {
|
|
||||||
Component.call(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
BalanceComponent.prototype.render = function () {
|
|
||||||
const props = this.props
|
|
||||||
const { token, network, assetImages } = props
|
|
||||||
const address = token && token.address
|
|
||||||
const image = assetImages && address ? assetImages[token.address] : undefined
|
|
||||||
|
|
||||||
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: 50,
|
|
||||||
address,
|
|
||||||
network,
|
|
||||||
image,
|
|
||||||
}),
|
|
||||||
|
|
||||||
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 { 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', {}, h(UserPreferencedCurrencyDisplay, {
|
|
||||||
value: balanceValue,
|
|
||||||
type: PRIMARY,
|
|
||||||
ethNumberOfDecimals: 3,
|
|
||||||
})),
|
|
||||||
|
|
||||||
showFiat && h(UserPreferencedCurrencyDisplay, {
|
|
||||||
value: balanceValue,
|
|
||||||
type: SECONDARY,
|
|
||||||
ethNumberOfDecimals: 3,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
|
|
||||||
if (formattedBalance === 'None') return formattedBalance
|
|
||||||
if (conversionRate === 0) return 'N/A'
|
|
||||||
|
|
||||||
const splitBalance = formattedBalance.split(' ')
|
|
||||||
|
|
||||||
const convertedNumber = (Number(splitBalance[0]) * conversionRate)
|
|
||||||
const wholePart = Math.floor(convertedNumber)
|
|
||||||
const decimalPart = convertedNumber - wholePart
|
|
||||||
|
|
||||||
return wholePart + Number(decimalPart.toPrecision(2))
|
|
||||||
}
|
|
|
@ -1,319 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import ethUtil from 'ethereumjs-util'
|
|
||||||
import { checkExistingAddresses } from './util'
|
|
||||||
import { tokenInfoGetter } from '../../../token-util'
|
|
||||||
import { DEFAULT_ROUTE, CONFIRM_ADD_TOKEN_ROUTE } from '../../../routes'
|
|
||||||
import TextField from '../../text-field'
|
|
||||||
import TokenList from './token-list'
|
|
||||||
import TokenSearch from './token-search'
|
|
||||||
import PageContainer from '../../page-container'
|
|
||||||
import { Tabs, Tab } from '../../tabs'
|
|
||||||
|
|
||||||
const emptyAddr = '0x0000000000000000000000000000000000000000'
|
|
||||||
const SEARCH_TAB = 'SEARCH'
|
|
||||||
const CUSTOM_TOKEN_TAB = 'CUSTOM_TOKEN'
|
|
||||||
|
|
||||||
class AddToken extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
history: PropTypes.object,
|
|
||||||
setPendingTokens: PropTypes.func,
|
|
||||||
pendingTokens: PropTypes.object,
|
|
||||||
clearPendingTokens: PropTypes.func,
|
|
||||||
tokens: PropTypes.array,
|
|
||||||
identities: PropTypes.object,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
customAddress: '',
|
|
||||||
customSymbol: '',
|
|
||||||
customDecimals: 0,
|
|
||||||
searchResults: [],
|
|
||||||
selectedTokens: {},
|
|
||||||
tokenSelectorError: null,
|
|
||||||
customAddressError: null,
|
|
||||||
customSymbolError: null,
|
|
||||||
customDecimalsError: null,
|
|
||||||
autoFilled: false,
|
|
||||||
displayedTab: SEARCH_TAB,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.tokenInfoGetter = tokenInfoGetter()
|
|
||||||
const { pendingTokens = {} } = this.props
|
|
||||||
const pendingTokenKeys = Object.keys(pendingTokens)
|
|
||||||
|
|
||||||
if (pendingTokenKeys.length > 0) {
|
|
||||||
let selectedTokens = {}
|
|
||||||
let customToken = {}
|
|
||||||
|
|
||||||
pendingTokenKeys.forEach(tokenAddress => {
|
|
||||||
const token = pendingTokens[tokenAddress]
|
|
||||||
const { isCustom } = token
|
|
||||||
|
|
||||||
if (isCustom) {
|
|
||||||
customToken = { ...token }
|
|
||||||
} else {
|
|
||||||
selectedTokens = { ...selectedTokens, [tokenAddress]: { ...token } }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
|
||||||
address: customAddress = '',
|
|
||||||
symbol: customSymbol = '',
|
|
||||||
decimals: customDecimals = 0,
|
|
||||||
} = customToken
|
|
||||||
|
|
||||||
const displayedTab = Object.keys(selectedTokens).length > 0 ? SEARCH_TAB : CUSTOM_TOKEN_TAB
|
|
||||||
this.setState({ selectedTokens, customAddress, customSymbol, customDecimals, displayedTab })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToggleToken (token) {
|
|
||||||
const { address } = token
|
|
||||||
const { selectedTokens = {} } = this.state
|
|
||||||
const selectedTokensCopy = { ...selectedTokens }
|
|
||||||
|
|
||||||
if (address in selectedTokensCopy) {
|
|
||||||
delete selectedTokensCopy[address]
|
|
||||||
} else {
|
|
||||||
selectedTokensCopy[address] = token
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
selectedTokens: selectedTokensCopy,
|
|
||||||
tokenSelectorError: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError () {
|
|
||||||
const {
|
|
||||||
tokenSelectorError,
|
|
||||||
customAddressError,
|
|
||||||
customSymbolError,
|
|
||||||
customDecimalsError,
|
|
||||||
} = this.state
|
|
||||||
|
|
||||||
return tokenSelectorError || customAddressError || customSymbolError || customDecimalsError
|
|
||||||
}
|
|
||||||
|
|
||||||
hasSelected () {
|
|
||||||
const { customAddress = '', selectedTokens = {} } = this.state
|
|
||||||
return customAddress || Object.keys(selectedTokens).length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNext () {
|
|
||||||
if (this.hasError()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.hasSelected()) {
|
|
||||||
this.setState({ tokenSelectorError: this.context.t('mustSelectOne') })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { setPendingTokens, history } = this.props
|
|
||||||
const {
|
|
||||||
customAddress: address,
|
|
||||||
customSymbol: symbol,
|
|
||||||
customDecimals: decimals,
|
|
||||||
selectedTokens,
|
|
||||||
} = this.state
|
|
||||||
|
|
||||||
const customToken = {
|
|
||||||
address,
|
|
||||||
symbol,
|
|
||||||
decimals,
|
|
||||||
}
|
|
||||||
|
|
||||||
setPendingTokens({ customToken, selectedTokens })
|
|
||||||
history.push(CONFIRM_ADD_TOKEN_ROUTE)
|
|
||||||
}
|
|
||||||
|
|
||||||
async attemptToAutoFillTokenParams (address) {
|
|
||||||
const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address)
|
|
||||||
|
|
||||||
const autoFilled = Boolean(symbol && decimals)
|
|
||||||
this.setState({ autoFilled })
|
|
||||||
this.handleCustomSymbolChange(symbol || '')
|
|
||||||
this.handleCustomDecimalsChange(decimals)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCustomAddressChange (value) {
|
|
||||||
const customAddress = value.trim()
|
|
||||||
this.setState({
|
|
||||||
customAddress,
|
|
||||||
customAddressError: null,
|
|
||||||
tokenSelectorError: null,
|
|
||||||
autoFilled: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const isValidAddress = ethUtil.isValidAddress(customAddress)
|
|
||||||
const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase()
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
case !isValidAddress:
|
|
||||||
this.setState({
|
|
||||||
customAddressError: this.context.t('invalidAddress'),
|
|
||||||
customSymbol: '',
|
|
||||||
customDecimals: 0,
|
|
||||||
customSymbolError: null,
|
|
||||||
customDecimalsError: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
case Boolean(this.props.identities[standardAddress]):
|
|
||||||
this.setState({
|
|
||||||
customAddressError: this.context.t('personalAddressDetected'),
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
case checkExistingAddresses(customAddress, this.props.tokens):
|
|
||||||
this.setState({
|
|
||||||
customAddressError: this.context.t('tokenAlreadyAdded'),
|
|
||||||
})
|
|
||||||
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
if (customAddress !== emptyAddr) {
|
|
||||||
this.attemptToAutoFillTokenParams(customAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCustomSymbolChange (value) {
|
|
||||||
const customSymbol = value.trim()
|
|
||||||
const symbolLength = customSymbol.length
|
|
||||||
let customSymbolError = null
|
|
||||||
|
|
||||||
if (symbolLength <= 0 || symbolLength >= 10) {
|
|
||||||
customSymbolError = this.context.t('symbolBetweenZeroTen')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ customSymbol, customSymbolError })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCustomDecimalsChange (value) {
|
|
||||||
const customDecimals = value.trim()
|
|
||||||
const validDecimals = customDecimals !== null &&
|
|
||||||
customDecimals !== '' &&
|
|
||||||
customDecimals >= 0 &&
|
|
||||||
customDecimals <= 36
|
|
||||||
let customDecimalsError = null
|
|
||||||
|
|
||||||
if (!validDecimals) {
|
|
||||||
customDecimalsError = this.context.t('decimalsMustZerotoTen')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ customDecimals, customDecimalsError })
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustomTokenForm () {
|
|
||||||
const {
|
|
||||||
customAddress,
|
|
||||||
customSymbol,
|
|
||||||
customDecimals,
|
|
||||||
customAddressError,
|
|
||||||
customSymbolError,
|
|
||||||
customDecimalsError,
|
|
||||||
autoFilled,
|
|
||||||
} = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="add-token__custom-token-form">
|
|
||||||
<TextField
|
|
||||||
id="custom-address"
|
|
||||||
label={this.context.t('tokenAddress')}
|
|
||||||
type="text"
|
|
||||||
value={customAddress}
|
|
||||||
onChange={e => this.handleCustomAddressChange(e.target.value)}
|
|
||||||
error={customAddressError}
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="custom-symbol"
|
|
||||||
label={this.context.t('tokenSymbol')}
|
|
||||||
type="text"
|
|
||||||
value={customSymbol}
|
|
||||||
onChange={e => this.handleCustomSymbolChange(e.target.value)}
|
|
||||||
error={customSymbolError}
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
disabled={autoFilled}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="custom-decimals"
|
|
||||||
label={this.context.t('decimal')}
|
|
||||||
type="number"
|
|
||||||
value={customDecimals}
|
|
||||||
onChange={e => this.handleCustomDecimalsChange(e.target.value)}
|
|
||||||
error={customDecimalsError}
|
|
||||||
fullWidth
|
|
||||||
margin="normal"
|
|
||||||
disabled={autoFilled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSearchToken () {
|
|
||||||
const { tokenSelectorError, selectedTokens, searchResults } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="add-token__search-token">
|
|
||||||
<TokenSearch
|
|
||||||
onSearch={({ results = [] }) => this.setState({ searchResults: results })}
|
|
||||||
error={tokenSelectorError}
|
|
||||||
/>
|
|
||||||
<div className="add-token__token-list">
|
|
||||||
<TokenList
|
|
||||||
results={searchResults}
|
|
||||||
selectedTokens={selectedTokens}
|
|
||||||
onToggleToken={token => this.handleToggleToken(token)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTabs () {
|
|
||||||
return (
|
|
||||||
<Tabs>
|
|
||||||
<Tab name={this.context.t('search')}>
|
|
||||||
{ this.renderSearchToken() }
|
|
||||||
</Tab>
|
|
||||||
<Tab name={this.context.t('customToken')}>
|
|
||||||
{ this.renderCustomTokenForm() }
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { history, clearPendingTokens } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContainer
|
|
||||||
title={this.context.t('addTokens')}
|
|
||||||
tabsComponent={this.renderTabs()}
|
|
||||||
onSubmit={() => this.handleNext()}
|
|
||||||
disabled={this.hasError() || !this.hasSelected()}
|
|
||||||
onCancel={() => {
|
|
||||||
clearPendingTokens()
|
|
||||||
history.push(DEFAULT_ROUTE)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AddToken
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import AddToken from './add-token.component'
|
|
||||||
|
|
||||||
const { setPendingTokens, clearPendingTokens } = require('../../../actions')
|
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask }) => {
|
|
||||||
const { identities, tokens, pendingTokens } = metamask
|
|
||||||
return {
|
|
||||||
identities,
|
|
||||||
tokens,
|
|
||||||
pendingTokens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
|
||||||
return {
|
|
||||||
setPendingTokens: tokens => dispatch(setPendingTokens(tokens)),
|
|
||||||
clearPendingTokens: () => dispatch(clearPendingTokens()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(AddToken)
|
|
|
@ -1,2 +0,0 @@
|
||||||
import AddToken from './add-token.container'
|
|
||||||
module.exports = AddToken
|
|
|
@ -1,25 +0,0 @@
|
||||||
@import './token-list/index';
|
|
||||||
|
|
||||||
.add-token {
|
|
||||||
&__custom-token-form {
|
|
||||||
padding: 8px 16px 16px;
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]:hover::-webkit-inner-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__search-token {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__token-list {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
import TokenList from './token-list.container'
|
|
||||||
module.exports = TokenList
|
|
|
@ -1,65 +0,0 @@
|
||||||
@import './token-list-placeholder/index';
|
|
||||||
|
|
||||||
.token-list {
|
|
||||||
&__title {
|
|
||||||
font-size: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__tokens-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__token {
|
|
||||||
transition: 200ms ease-in-out;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 2px solid rgba($malibu-blue, .5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--selected {
|
|
||||||
border: 2px solid $malibu-blue !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--disabled {
|
|
||||||
opacity: .4;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__token-icon {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: contain;
|
|
||||||
background-position: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: $white;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba($black, .24);
|
|
||||||
margin-right: 12px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__token-data {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__token-name {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
import TokenListPlaceholder from './token-list-placeholder.component'
|
|
||||||
module.exports = TokenListPlaceholder
|
|
|
@ -1,23 +0,0 @@
|
||||||
.token-list-placeholder {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 36px;
|
|
||||||
flex-direction: column;
|
|
||||||
line-height: 22px;
|
|
||||||
opacity: .5;
|
|
||||||
|
|
||||||
&__text {
|
|
||||||
color: $silver-chalice;
|
|
||||||
width: 50%;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__link {
|
|
||||||
color: $curious-blue;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
export default class TokenListPlaceholder extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="token-list-placeholder">
|
|
||||||
<img src="images/tokensearch.svg" />
|
|
||||||
<div className="token-list-placeholder__text">
|
|
||||||
{ this.context.t('addAcquiredTokens') }
|
|
||||||
</div>
|
|
||||||
<a
|
|
||||||
className="token-list-placeholder__link"
|
|
||||||
href="https://metamask.zendesk.com/hc/en-us/articles/360015489031"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{ this.context.t('learnMore') }
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import { checkExistingAddresses } from '../util'
|
|
||||||
import TokenListPlaceholder from './token-list-placeholder'
|
|
||||||
|
|
||||||
export default class InfoBox extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
tokens: PropTypes.array,
|
|
||||||
results: PropTypes.array,
|
|
||||||
selectedTokens: PropTypes.object,
|
|
||||||
onToggleToken: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { results = [], selectedTokens = {}, onToggleToken, tokens = [] } = this.props
|
|
||||||
|
|
||||||
return results.length === 0
|
|
||||||
? <TokenListPlaceholder />
|
|
||||||
: (
|
|
||||||
<div className="token-list">
|
|
||||||
<div className="token-list__title">
|
|
||||||
{ this.context.t('searchResults') }
|
|
||||||
</div>
|
|
||||||
<div className="token-list__tokens-container">
|
|
||||||
{
|
|
||||||
Array(6).fill(undefined)
|
|
||||||
.map((_, i) => {
|
|
||||||
const { logo, symbol, name, address } = results[i] || {}
|
|
||||||
const tokenAlreadyAdded = checkExistingAddresses(address, tokens)
|
|
||||||
|
|
||||||
return Boolean(logo || symbol || name) && (
|
|
||||||
<div
|
|
||||||
className={classnames('token-list__token', {
|
|
||||||
'token-list__token--selected': selectedTokens[address],
|
|
||||||
'token-list__token--disabled': tokenAlreadyAdded,
|
|
||||||
})}
|
|
||||||
onClick={() => !tokenAlreadyAdded && onToggleToken(results[i])}
|
|
||||||
key={i}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="token-list__token-icon"
|
|
||||||
style={{ backgroundImage: logo && `url(images/contract/${logo})` }}>
|
|
||||||
</div>
|
|
||||||
<div className="token-list__token-data">
|
|
||||||
<span className="token-list__token-name">{ `${name} (${symbol})` }</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import TokenList from './token-list.component'
|
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask }) => {
|
|
||||||
const { tokens } = metamask
|
|
||||||
return {
|
|
||||||
tokens,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(TokenList)
|
|
|
@ -1,2 +0,0 @@
|
||||||
import TokenSearch from './token-search.component'
|
|
||||||
module.exports = TokenSearch
|
|
|
@ -1,85 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import contractMap from 'eth-contract-metadata'
|
|
||||||
import Fuse from 'fuse.js'
|
|
||||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
|
||||||
import TextField from '../../../text-field'
|
|
||||||
|
|
||||||
const contractList = Object.entries(contractMap)
|
|
||||||
.map(([ _, tokenData]) => tokenData)
|
|
||||||
.filter(tokenData => Boolean(tokenData.erc20))
|
|
||||||
|
|
||||||
const fuse = new Fuse(contractList, {
|
|
||||||
shouldSort: true,
|
|
||||||
threshold: 0.45,
|
|
||||||
location: 0,
|
|
||||||
distance: 100,
|
|
||||||
maxPatternLength: 32,
|
|
||||||
minMatchCharLength: 1,
|
|
||||||
keys: [
|
|
||||||
{ name: 'name', weight: 0.5 },
|
|
||||||
{ name: 'symbol', weight: 0.5 },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default class TokenSearch extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
error: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
onSearch: PropTypes.func,
|
|
||||||
error: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
searchQuery: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearch (searchQuery) {
|
|
||||||
this.setState({ searchQuery })
|
|
||||||
const fuseSearchResult = fuse.search(searchQuery)
|
|
||||||
const addressSearchResult = contractList.filter(token => {
|
|
||||||
return token.address.toLowerCase() === searchQuery.toLowerCase()
|
|
||||||
})
|
|
||||||
const results = [...addressSearchResult, ...fuseSearchResult]
|
|
||||||
this.props.onSearch({ searchQuery, results })
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAdornment () {
|
|
||||||
return (
|
|
||||||
<InputAdornment
|
|
||||||
position="start"
|
|
||||||
style={{ marginRight: '12px' }}
|
|
||||||
>
|
|
||||||
<img src="images/search.svg" />
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { error } = this.props
|
|
||||||
const { searchQuery } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TextField
|
|
||||||
id="search-tokens"
|
|
||||||
placeholder={this.context.t('searchTokens')}
|
|
||||||
type="text"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={e => this.handleSearch(e.target.value)}
|
|
||||||
error={error}
|
|
||||||
fullWidth
|
|
||||||
startAdornment={this.renderAdornment()}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import R from 'ramda'
|
|
||||||
|
|
||||||
export function checkExistingAddresses (address, tokenList = []) {
|
|
||||||
if (!address) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchesAddress = existingToken => {
|
|
||||||
return existingToken.address.toLowerCase() === address.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
return R.any(matchesAddress)(tokenList)
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
const { connect } = require('react-redux')
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const { Redirect } = require('react-router-dom')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const MetamaskRoute = require('./metamask-route')
|
|
||||||
const { UNLOCK_ROUTE, INITIALIZE_ROUTE } = require('../../routes')
|
|
||||||
|
|
||||||
const Authenticated = props => {
|
|
||||||
const { isUnlocked, isInitialized } = props
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
case isUnlocked && isInitialized:
|
|
||||||
return h(MetamaskRoute, { ...props })
|
|
||||||
case !isInitialized:
|
|
||||||
return h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
|
|
||||||
default:
|
|
||||||
return h(Redirect, { to: { pathname: UNLOCK_ROUTE } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Authenticated.propTypes = {
|
|
||||||
isUnlocked: PropTypes.bool,
|
|
||||||
isInitialized: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const { metamask: { isUnlocked, isInitialized } } = state
|
|
||||||
return {
|
|
||||||
isUnlocked,
|
|
||||||
isInitialized,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(Authenticated)
|
|
|
@ -1,25 +0,0 @@
|
||||||
const { connect } = require('react-redux')
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const { Redirect } = require('react-router-dom')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const { INITIALIZE_ROUTE } = require('../../routes')
|
|
||||||
const MetamaskRoute = require('./metamask-route')
|
|
||||||
|
|
||||||
const Initialized = props => {
|
|
||||||
return props.isInitialized
|
|
||||||
? h(MetamaskRoute, { ...props })
|
|
||||||
: h(Redirect, { to: { pathname: INITIALIZE_ROUTE } })
|
|
||||||
}
|
|
||||||
|
|
||||||
Initialized.propTypes = {
|
|
||||||
isInitialized: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const { metamask: { isInitialized } } = state
|
|
||||||
return {
|
|
||||||
isInitialized,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(Initialized)
|
|
|
@ -1,28 +0,0 @@
|
||||||
const { connect } = require('react-redux')
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const { Route } = require('react-router-dom')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
|
|
||||||
const MetamaskRoute = ({ component, mascaraComponent, isMascara, ...props }) => {
|
|
||||||
return (
|
|
||||||
h(Route, {
|
|
||||||
...props,
|
|
||||||
component: isMascara && mascaraComponent ? mascaraComponent : component,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
MetamaskRoute.propTypes = {
|
|
||||||
component: PropTypes.func,
|
|
||||||
mascaraComponent: PropTypes.func,
|
|
||||||
isMascara: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const { metamask: { isMascara } } = state
|
|
||||||
return {
|
|
||||||
isMascara,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(MetamaskRoute)
|
|
|
@ -1,203 +0,0 @@
|
||||||
const { Component } = require('react')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const { connect } = require('react-redux')
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const ReactMarkdown = require('react-markdown')
|
|
||||||
const linker = require('extension-link-enabler')
|
|
||||||
const generateLostAccountsNotice = require('../../../lib/lost-accounts-notice')
|
|
||||||
const findDOMNode = require('react-dom').findDOMNode
|
|
||||||
const actions = require('../../actions')
|
|
||||||
const { DEFAULT_ROUTE } = require('../../routes')
|
|
||||||
|
|
||||||
class Notice extends Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
disclaimerDisabled: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
if (!this.props.notice) {
|
|
||||||
this.props.history.push(DEFAULT_ROUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
// eslint-disable-next-line react/no-find-dom-node
|
|
||||||
var node = findDOMNode(this)
|
|
||||||
linker.setupListener(node)
|
|
||||||
if (document.getElementsByClassName('notice-box')[0].clientHeight < 310) {
|
|
||||||
this.setState({ disclaimerDisabled: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (!nextProps.notice) {
|
|
||||||
this.props.history.push(DEFAULT_ROUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
// eslint-disable-next-line react/no-find-dom-node
|
|
||||||
var node = findDOMNode(this)
|
|
||||||
linker.teardownListener(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAccept () {
|
|
||||||
this.setState({ disclaimerDisabled: true })
|
|
||||||
this.props.onConfirm()
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { notice = {} } = this.props
|
|
||||||
const { title, date, body } = notice
|
|
||||||
const { disclaimerDisabled } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
h('.flex-column.flex-center.flex-grow', {
|
|
||||||
style: {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
}, [
|
|
||||||
h('h3.flex-center.text-transform-uppercase.terms-header', {
|
|
||||||
style: {
|
|
||||||
background: '#EBEBEB',
|
|
||||||
color: '#AEAEAE',
|
|
||||||
width: '100%',
|
|
||||||
fontSize: '20px',
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: 6,
|
|
||||||
},
|
|
||||||
}, [
|
|
||||||
title,
|
|
||||||
]),
|
|
||||||
|
|
||||||
h('h5.flex-center.text-transform-uppercase.terms-header', {
|
|
||||||
style: {
|
|
||||||
background: '#EBEBEB',
|
|
||||||
color: '#AEAEAE',
|
|
||||||
marginBottom: 24,
|
|
||||||
width: '100%',
|
|
||||||
fontSize: '20px',
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: 6,
|
|
||||||
},
|
|
||||||
}, [
|
|
||||||
date,
|
|
||||||
]),
|
|
||||||
|
|
||||||
h('style', `
|
|
||||||
|
|
||||||
.markdown {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown h1, .markdown h2, .markdown h3 {
|
|
||||||
margin: 10px 0;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.markdown em {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown p {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markdown a {
|
|
||||||
color: #df6b0e;
|
|
||||||
}
|
|
||||||
|
|
||||||
`),
|
|
||||||
|
|
||||||
h('div.markdown', {
|
|
||||||
onScroll: (e) => {
|
|
||||||
var object = e.currentTarget
|
|
||||||
if (object.offsetHeight + object.scrollTop + 100 >= object.scrollHeight) {
|
|
||||||
this.setState({ disclaimerDisabled: false })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
background: 'rgb(235, 235, 235)',
|
|
||||||
height: '310px',
|
|
||||||
padding: '6px',
|
|
||||||
width: '90%',
|
|
||||||
overflowY: 'scroll',
|
|
||||||
scroll: 'auto',
|
|
||||||
},
|
|
||||||
}, [
|
|
||||||
h(ReactMarkdown, {
|
|
||||||
className: 'notice-box',
|
|
||||||
source: body,
|
|
||||||
skipHtml: true,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
|
|
||||||
h('button.primary', {
|
|
||||||
disabled: disclaimerDisabled,
|
|
||||||
onClick: () => this.handleAccept(),
|
|
||||||
style: {
|
|
||||||
marginTop: '18px',
|
|
||||||
},
|
|
||||||
}, 'Accept'),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const { metamask } = state
|
|
||||||
const { noActiveNotices, nextUnreadNotice, lostAccounts } = metamask
|
|
||||||
|
|
||||||
return {
|
|
||||||
noActiveNotices,
|
|
||||||
nextUnreadNotice,
|
|
||||||
lostAccounts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Notice.propTypes = {
|
|
||||||
notice: PropTypes.object,
|
|
||||||
onConfirm: PropTypes.func,
|
|
||||||
history: PropTypes.object,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
|
||||||
return {
|
|
||||||
markNoticeRead: nextUnreadNotice => dispatch(actions.markNoticeRead(nextUnreadNotice)),
|
|
||||||
markAccountsFound: () => dispatch(actions.markAccountsFound()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|
||||||
const { noActiveNotices, nextUnreadNotice, lostAccounts } = stateProps
|
|
||||||
const { markNoticeRead, markAccountsFound } = dispatchProps
|
|
||||||
|
|
||||||
let notice
|
|
||||||
let onConfirm
|
|
||||||
|
|
||||||
if (!noActiveNotices) {
|
|
||||||
notice = nextUnreadNotice
|
|
||||||
onConfirm = () => markNoticeRead(nextUnreadNotice)
|
|
||||||
} else if (lostAccounts && lostAccounts.length > 0) {
|
|
||||||
notice = generateLostAccountsNotice(lostAccounts)
|
|
||||||
onConfirm = () => markAccountsFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...stateProps,
|
|
||||||
...dispatchProps,
|
|
||||||
...ownProps,
|
|
||||||
notice,
|
|
||||||
onConfirm,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Notice)
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './token-balance.container'
|
|
|
@ -1,23 +0,0 @@
|
||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
|
|
||||||
export default class TokenBalance extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
string: PropTypes.string,
|
|
||||||
symbol: PropTypes.string,
|
|
||||||
error: PropTypes.string,
|
|
||||||
className: PropTypes.string,
|
|
||||||
withSymbol: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { className, string, withSymbol, symbol } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classnames('hide-text-overflow', className)}>
|
|
||||||
{ string + (withSymbol ? ` ${symbol}` : '') }
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
import withTokenTracker from '../../higher-order-components/with-token-tracker'
|
|
||||||
import TokenBalance from './token-balance.component'
|
|
||||||
import selectors from '../../selectors'
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
userAddress: selectors.getSelectedAddress(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
connect(mapStateToProps),
|
|
||||||
withTokenTracker
|
|
||||||
)(TokenBalance)
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
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 ethNetProps = require('eth-net-props')
|
||||||
|
const selectors = require('../selectors')
|
||||||
|
const actions = require('../actions')
|
||||||
|
const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
|
||||||
|
|
||||||
|
const TokenMenuDropdown = require('./dropdowns/token-menu-dropdown.js')
|
||||||
|
|
||||||
|
function mapStateToProps (state) {
|
||||||
|
return {
|
||||||
|
network: state.metamask.network,
|
||||||
|
currentCurrency: state.metamask.currentCurrency,
|
||||||
|
selectedTokenAddress: state.metamask.selectedTokenAddress,
|
||||||
|
userAddress: selectors.getSelectedAddress(state),
|
||||||
|
contractExchangeRates: state.metamask.contractExchangeRates,
|
||||||
|
conversionRate: state.metamask.conversionRate,
|
||||||
|
sidebarOpen: state.appState.sidebar.isOpen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps (dispatch) {
|
||||||
|
return {
|
||||||
|
setSelectedToken: address => dispatch(actions.setSelectedToken(address)),
|
||||||
|
hideSidebar: () => dispatch(actions.hideSidebar()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenCell)
|
||||||
|
|
||||||
|
inherits(TokenCell, Component)
|
||||||
|
function TokenCell () {
|
||||||
|
Component.call(this)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
tokenMenuOpen: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenCell.prototype.render = function () {
|
||||||
|
const { tokenMenuOpen } = this.state
|
||||||
|
const props = this.props
|
||||||
|
const {
|
||||||
|
address,
|
||||||
|
symbol,
|
||||||
|
string,
|
||||||
|
network,
|
||||||
|
setSelectedToken,
|
||||||
|
selectedTokenAddress,
|
||||||
|
contractExchangeRates,
|
||||||
|
conversionRate,
|
||||||
|
hideSidebar,
|
||||||
|
sidebarOpen,
|
||||||
|
currentCurrency,
|
||||||
|
// userAddress,
|
||||||
|
image,
|
||||||
|
} = props
|
||||||
|
let currentTokenToFiatRate
|
||||||
|
let currentTokenInFiat
|
||||||
|
let formattedFiat = ''
|
||||||
|
|
||||||
|
if (contractExchangeRates[address]) {
|
||||||
|
currentTokenToFiatRate = multiplyCurrencies(
|
||||||
|
contractExchangeRates[address],
|
||||||
|
conversionRate
|
||||||
|
)
|
||||||
|
currentTokenInFiat = conversionUtil(string, {
|
||||||
|
fromNumericBase: 'dec',
|
||||||
|
fromCurrency: symbol,
|
||||||
|
toCurrency: currentCurrency.toUpperCase(),
|
||||||
|
numberOfDecimals: 2,
|
||||||
|
conversionRate: currentTokenToFiatRate,
|
||||||
|
})
|
||||||
|
formattedFiat = currentTokenInFiat.toString() === '0'
|
||||||
|
? ''
|
||||||
|
: `${currentTokenInFiat} ${currentCurrency.toUpperCase()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol
|
||||||
|
|
||||||
|
return (
|
||||||
|
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)
|
||||||
|
selectedTokenAddress !== address && sidebarOpen && hideSidebar()
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
|
||||||
|
h(Identicon, {
|
||||||
|
className: 'token-list-item__identicon',
|
||||||
|
diameter: 50,
|
||||||
|
address,
|
||||||
|
network,
|
||||||
|
image,
|
||||||
|
}),
|
||||||
|
|
||||||
|
h('div.token-list-item__balance-ellipsis', null, [
|
||||||
|
h('div.token-list-item__balance-wrapper', null, [
|
||||||
|
h('div.token-list-item__token-balance', `${string || 0}`),
|
||||||
|
h('div.token-list-item__token-symbol', symbol),
|
||||||
|
showFiat && h('div.token-list-item__fiat-amount', {
|
||||||
|
style: {},
|
||||||
|
}, formattedFiat),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('i.fa.fa-ellipsis-h.fa-lg.token-list-item__ellipsis.cursor-pointer', {
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
this.setState({ tokenMenuOpen: true })
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
]),
|
||||||
|
|
||||||
|
|
||||||
|
tokenMenuOpen && h(TokenMenuDropdown, {
|
||||||
|
onClose: () => this.setState({ tokenMenuOpen: false }),
|
||||||
|
token: { symbol, address },
|
||||||
|
}),
|
||||||
|
|
||||||
|
/*
|
||||||
|
h('button', {
|
||||||
|
onClick: this.send.bind(this, address),
|
||||||
|
}, 'SEND'),
|
||||||
|
*/
|
||||||
|
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenCell.prototype.send = function (address, event) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
const url = tokenFactoryFor(address)
|
||||||
|
if (url) {
|
||||||
|
navigateTo(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenCell.prototype.view = function (address, userAddress, network, event) {
|
||||||
|
const url = ethNetProps.explorerLinks.getExplorerTokenLinkFor(address, userAddress, network)
|
||||||
|
if (url) {
|
||||||
|
navigateTo(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateTo (url) {
|
||||||
|
global.platform.openWindow({ url })
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenFactoryFor (tokenAddress) {
|
||||||
|
return `https://tokenfactory.surge.sh/#/token/${tokenAddress}`
|
||||||
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './token-currency-display.component'
|
|
|
@ -1,54 +0,0 @@
|
||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import CurrencyDisplay from '../currency-display/currency-display.component'
|
|
||||||
import { getTokenData } from '../../helpers/transactions.util'
|
|
||||||
import { getTokenValue, calcTokenAmount } from '../../token-util'
|
|
||||||
|
|
||||||
export default class TokenCurrencyDisplay extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
transactionData: PropTypes.string,
|
|
||||||
token: PropTypes.object,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
displayValue: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.setDisplayValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
const { transactionData } = this.props
|
|
||||||
const { transactionData: prevTransactionData } = prevProps
|
|
||||||
|
|
||||||
if (transactionData !== prevTransactionData) {
|
|
||||||
this.setDisplayValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setDisplayValue () {
|
|
||||||
const { transactionData: data, token } = this.props
|
|
||||||
const { decimals = '', symbol = '' } = token
|
|
||||||
const tokenData = getTokenData(data)
|
|
||||||
|
|
||||||
let displayValue
|
|
||||||
|
|
||||||
if (tokenData.params && tokenData.params.length) {
|
|
||||||
const tokenValue = getTokenValue(tokenData.params)
|
|
||||||
const tokenAmount = calcTokenAmount(tokenValue, decimals)
|
|
||||||
displayValue = `${tokenAmount} ${symbol}`
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ displayValue })
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<CurrencyDisplay
|
|
||||||
{...this.props}
|
|
||||||
displayValue={this.state.displayValue}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue