New iteration of cleanup

This commit is contained in:
Victor Baranov 2019-09-03 21:47:43 +03:00
parent 5b5ec4585d
commit 799baff067
26 changed files with 161 additions and 1153 deletions

View File

@ -1,7 +1,7 @@
const injectCss = require('inject-css')
const SwController = require('sw-controller')
const SwStream = require('sw-stream')
const MetaMaskUiCss = require('../../ui/css')
const MetaMaskUiCss = require('../../old-ui/css')
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
const startPopup = require('../../app/scripts/popup-core')

View File

@ -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))
}

View File

@ -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

View File

@ -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)

View File

@ -1,2 +0,0 @@
import AddToken from './add-token.container'
module.exports = AddToken

View File

@ -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;
}
}

View File

@ -1,2 +0,0 @@
import TokenList from './token-list.container'
module.exports = TokenList

View File

@ -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;
}
}

View File

@ -1,2 +0,0 @@
import TokenListPlaceholder from './token-list-placeholder.component'
module.exports = TokenListPlaceholder

View File

@ -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;
}
}

View File

@ -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>
)
}
}

View File

@ -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>
)
}
}

View File

@ -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)

View File

@ -1,2 +0,0 @@
import TokenSearch from './token-search.component'
module.exports = TokenSearch

View File

@ -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()}
/>
)
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -1 +0,0 @@
export { default } from './token-balance.container'

View File

@ -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>
)
}
}

View File

@ -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)

View File

@ -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}`
}

View File

@ -1 +0,0 @@
export { default } from './token-currency-display.component'

View File

@ -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}
/>
)
}
}