(Feature) Remove spam tokens / Deactivate tokens refresh fix (#1331)
* Fix load current session * Fixs useMemo usage in filteredData * Type fetchTokens * Type useFetchTokens * Type setCurrencyBalances * Fixs ADD_SAFE reducer for existing safe, uses mergeDeep instead of merge, now the active tokens for the safe are not overwritten * Fix save selected currency * Adds excludeSpamTokens param in fetchTokenCurrenciesBalances * Adds onlyTrustedTokens param in fetchTokenCurrenciesBalances * Merge with development * Remove onlyTrustedTokens param * Fix unnecesary changes * Replace Dispatch with ThunkDispatch * Fix import consistency * Type containsMethodByHash * Fix blacklisted addresses calculation * Adds types on updateActiveTokens Adds types on updateBlacklistedTokens * Refactor Tokens to TokenList, makes it functional component also fix blacklisted addresses calculation * Refactor Tokens to TokenList, makes it functional component also fix blacklisted addresses calculation * Refactor AddCustomToken, add types Removes actions from Tokens * Fix warning on useEffect Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
parent
8efafc1aaa
commit
33018172df
|
@ -11,9 +11,12 @@ export type BalanceEndpoint = {
|
|||
usdConversion: string
|
||||
}
|
||||
|
||||
const fetchTokenCurrenciesBalances = (safeAddress: string): Promise<AxiosResponse<BalanceEndpoint[]>> => {
|
||||
const fetchTokenCurrenciesBalances = (
|
||||
safeAddress: string,
|
||||
excludeSpamTokens = true,
|
||||
): Promise<AxiosResponse<BalanceEndpoint[]>> => {
|
||||
const apiUrl = getTxServiceHost()
|
||||
const url = `${apiUrl}safes/${safeAddress}/balances/usd/`
|
||||
const url = `${apiUrl}safes/${safeAddress}/balances/usd/?exclude_spam=${excludeSpamTokens}`
|
||||
|
||||
return axios.get(url, {
|
||||
params: {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
import { BalanceCurrencyList } from 'src/logic/currencyValues/store/model/currencyValues'
|
||||
|
||||
export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES'
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const setCurrencyBalances = createAction(SET_CURRENCY_BALANCES, (safeAddress, currencyBalances) => ({
|
||||
safeAddress,
|
||||
currencyBalances,
|
||||
}))
|
||||
export const setCurrencyBalances = createAction(
|
||||
SET_CURRENCY_BALANCES,
|
||||
(safeAddress: string, currencyBalances: BalanceCurrencyList) => ({
|
||||
safeAddress,
|
||||
currencyBalances,
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import updateSafe from './updateSafe'
|
||||
import { Set } from 'immutable'
|
||||
import { Dispatch } from 'redux'
|
||||
|
||||
// the selector uses ownProps argument/router props to get the address of the safe
|
||||
// so in order to use it I had to recreate the same structure
|
||||
|
@ -10,7 +12,7 @@ import updateSafe from './updateSafe'
|
|||
// },
|
||||
// })
|
||||
|
||||
const updateActiveTokens = (safeAddress, activeTokens) => async (dispatch) => {
|
||||
const updateActiveTokens = (safeAddress: string, activeTokens: Set<string>) => (dispatch: Dispatch): void => {
|
||||
dispatch(updateSafe({ address: safeAddress, activeTokens }))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import updateSafe from './updateSafe'
|
||||
import { Dispatch } from 'redux'
|
||||
import { Set } from 'immutable'
|
||||
|
||||
const updateBlacklistedTokens = (safeAddress, blacklistedTokens) => async (dispatch) => {
|
||||
const updateBlacklistedTokens = (safeAddress: string, blacklistedTokens: Set<string>) => (dispatch: Dispatch): void => {
|
||||
dispatch(updateSafe({ address: safeAddress, blacklistedTokens }))
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ export default handleActions(
|
|||
// with initial props and it would overwrite existing ones
|
||||
|
||||
if (state.hasIn(['safes', safe.address])) {
|
||||
return state.updateIn(['safes', safe.address], (prevSafe) => prevSafe.merge(safe))
|
||||
return state.updateIn(['safes', safe.address], (prevSafe) => prevSafe.mergeDeep(safe))
|
||||
}
|
||||
|
||||
return state.setIn(['safes', safe.address], makeSafe(safe))
|
||||
|
|
|
@ -36,7 +36,7 @@ const cancellationTransactionsSelector = (state: AppReduxState) => state[CANCELL
|
|||
|
||||
const incomingTransactionsSelector = (state: AppReduxState) => state[INCOMING_TRANSACTIONS_REDUCER_ID]
|
||||
|
||||
export const safeParamAddressFromStateSelector = (state: AppReduxState): string | undefined => {
|
||||
export const safeParamAddressFromStateSelector = (state: AppReduxState): string => {
|
||||
const match = matchPath<{ safeAddress: string }>(state.router.location.pathname, {
|
||||
path: `${SAFELIST_ADDRESS}/:safeAddress`,
|
||||
})
|
||||
|
@ -45,7 +45,7 @@ export const safeParamAddressFromStateSelector = (state: AppReduxState): string
|
|||
return checksumAddress(match.params.safeAddress)
|
||||
}
|
||||
|
||||
return undefined
|
||||
return ''
|
||||
}
|
||||
|
||||
export const safeParamAddressSelector = (
|
||||
|
|
|
@ -12,8 +12,10 @@ import { fetchTokenList } from 'src/logic/tokens/api'
|
|||
import { makeToken, Token } from 'src/logic/tokens/store/model/token'
|
||||
import { tokensSelector } from 'src/logic/tokens/store/selectors'
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import { store } from 'src/store'
|
||||
import { AppReduxState, store } from 'src/store'
|
||||
import { ensureOnce } from 'src/utils/singleton'
|
||||
import { ThunkDispatch } from 'redux-thunk'
|
||||
import { AnyAction } from 'redux'
|
||||
|
||||
const createStandardTokenContract = async () => {
|
||||
const web3 = getWeb3()
|
||||
|
@ -43,7 +45,7 @@ export const getStandardTokenContract = ensureOnce(createStandardTokenContract)
|
|||
|
||||
export const getERC721TokenContract = ensureOnce(createERC721TokenContract)
|
||||
|
||||
export const containsMethodByHash = async (contractAddress, methodHash) => {
|
||||
export const containsMethodByHash = async (contractAddress: string, methodHash: string): Promise<boolean> => {
|
||||
const web3 = getWeb3()
|
||||
const byteCode = await web3.eth.getCode(contractAddress)
|
||||
|
||||
|
@ -87,7 +89,10 @@ export const getTokenInfos = async (tokenAddress: string): Promise<Token | undef
|
|||
return token
|
||||
}
|
||||
|
||||
export const fetchTokens = () => async (dispatch, getState) => {
|
||||
export const fetchTokens = () => async (
|
||||
dispatch: ThunkDispatch<AppReduxState, undefined, AnyAction>,
|
||||
getState: () => AppReduxState,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const currentSavedTokens = tokensSelector(getState())
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@ import { Skeleton } from '@material-ui/lab'
|
|||
|
||||
import InfoIcon from 'src/assets/icons/info.svg'
|
||||
|
||||
import { useStyles } from './styles'
|
||||
|
||||
import Img from 'src/components/layout/Img'
|
||||
import Table from 'src/components/Table'
|
||||
import { cellWidth } from 'src/components/Table/TableHead'
|
||||
|
@ -33,25 +31,16 @@ import {
|
|||
} from 'src/routes/safe/components/Balances/dataFetcher'
|
||||
import { extendedSafeTokensSelector, grantedSelector } from 'src/routes/safe/container/selector'
|
||||
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { styles } from './styles'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
type Props = {
|
||||
showReceiveFunds: () => void
|
||||
showSendFunds: (tokenAddress: string) => void
|
||||
}
|
||||
|
||||
export type BalanceDataRow = List<{
|
||||
asset: {
|
||||
name: string
|
||||
address: string
|
||||
logoUri: string
|
||||
}
|
||||
assetOrder: string
|
||||
balance: string
|
||||
balanceOrder: number
|
||||
fixed: boolean
|
||||
value: string
|
||||
}>
|
||||
|
||||
type CurrencyTooltipProps = {
|
||||
valueWithCurrency: string
|
||||
balanceWithSymbol: string
|
||||
|
@ -84,16 +73,16 @@ const Coins = (props: Props): React.ReactElement => {
|
|||
const activeTokens = useSelector(extendedSafeTokensSelector)
|
||||
const currencyValues = useSelector(safeFiatBalancesListSelector)
|
||||
const granted = useSelector(grantedSelector)
|
||||
const [filteredData, setFilteredData] = React.useState<List<BalanceData>>(List())
|
||||
const { trackEvent } = useAnalytics()
|
||||
|
||||
useEffect(() => {
|
||||
trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Coins' })
|
||||
}, [trackEvent])
|
||||
|
||||
useMemo(() => {
|
||||
setFilteredData(getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate))
|
||||
}, [activeTokens, selectedCurrency, currencyValues, currencyRate])
|
||||
const filteredData: List<BalanceData> = useMemo(
|
||||
() => getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate),
|
||||
[activeTokens, selectedCurrency, currencyValues, currencyRate],
|
||||
)
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import { sm, xs } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
export const styles = createStyles({
|
||||
iconSmall: {
|
||||
fontSize: 16,
|
||||
},
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { addToken } from 'src/logic/tokens/store/actions/addToken'
|
||||
import fetchTokens from 'src/logic/tokens/store/actions/fetchTokens'
|
||||
import activateTokenForAllSafes from 'src/logic/safe/store/actions/activateTokenForAllSafes'
|
||||
import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens'
|
||||
import updateBlacklistedTokens from 'src/logic/safe/store/actions/updateBlacklistedTokens'
|
||||
|
||||
export default {
|
||||
fetchTokens,
|
||||
addToken,
|
||||
updateActiveTokens,
|
||||
updateBlacklistedTokens,
|
||||
activateTokenForAllSafes,
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { connect, useSelector } from 'react-redux'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import actions from './actions'
|
||||
import { styles } from './style'
|
||||
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
|
@ -16,27 +15,27 @@ import { orderedTokenListSelector } from 'src/logic/tokens/store/selectors'
|
|||
import AddCustomAssetComponent from 'src/routes/safe/components/Balances/Tokens/screens/AddCustomAsset'
|
||||
import AddCustomToken from 'src/routes/safe/components/Balances/Tokens/screens/AddCustomToken'
|
||||
import AssetsList from 'src/routes/safe/components/Balances/Tokens/screens/AssetsList'
|
||||
import TokenList from 'src/routes/safe/components/Balances/Tokens/screens/TokenList'
|
||||
|
||||
import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector'
|
||||
import { safeBlacklistedTokensSelector } from 'src/logic/safe/store/selectors'
|
||||
import { TokenList } from 'src/routes/safe/components/Balances/Tokens/screens/TokenList'
|
||||
|
||||
export const MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID = 'manage-tokens-close-modal-btn'
|
||||
|
||||
const Tokens = (props) => {
|
||||
const {
|
||||
activateTokenForAllSafes,
|
||||
addToken,
|
||||
classes,
|
||||
fetchTokens,
|
||||
modalScreen,
|
||||
onClose,
|
||||
safeAddress,
|
||||
updateActiveTokens,
|
||||
updateBlacklistedTokens,
|
||||
} = props
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
type Props = {
|
||||
safeAddress: string
|
||||
modalScreen: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const Tokens = (props: Props): React.ReactElement => {
|
||||
const { modalScreen, onClose, safeAddress } = props
|
||||
const tokens = useSelector(orderedTokenListSelector)
|
||||
const activeTokens = useSelector(extendedSafeTokensSelector)
|
||||
const blacklistedTokens = useSelector(safeBlacklistedTokensSelector)
|
||||
const classes = useStyles()
|
||||
const [activeScreen, setActiveScreen] = useState(modalScreen)
|
||||
|
||||
return (
|
||||
|
@ -54,26 +53,20 @@ const Tokens = (props) => {
|
|||
<TokenList
|
||||
activeTokens={activeTokens}
|
||||
blacklistedTokens={blacklistedTokens}
|
||||
fetchTokens={fetchTokens}
|
||||
safeAddress={safeAddress}
|
||||
setActiveScreen={setActiveScreen}
|
||||
tokens={tokens}
|
||||
updateActiveTokens={updateActiveTokens}
|
||||
updateBlacklistedTokens={updateBlacklistedTokens}
|
||||
/>
|
||||
)}
|
||||
{activeScreen === 'assetsList' && <AssetsList setActiveScreen={setActiveScreen} />}
|
||||
{activeScreen === 'addCustomToken' && (
|
||||
<AddCustomToken
|
||||
activateTokenForAllSafes={activateTokenForAllSafes}
|
||||
activeTokens={activeTokens}
|
||||
addToken={addToken}
|
||||
onClose={onClose}
|
||||
parentList={'tokenList'}
|
||||
safeAddress={safeAddress}
|
||||
setActiveScreen={setActiveScreen}
|
||||
tokens={tokens}
|
||||
updateActiveTokens={updateActiveTokens}
|
||||
/>
|
||||
)}
|
||||
{activeScreen === 'addCustomAsset' && (
|
||||
|
@ -83,6 +76,4 @@ const Tokens = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const TokenComponent = withStyles(styles as any)(Tokens)
|
||||
|
||||
export default connect(undefined, actions)(TokenComponent)
|
||||
export default Tokens
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import React, { useState } from 'react'
|
||||
import { FormSpy } from 'react-final-form'
|
||||
|
||||
|
@ -22,6 +22,12 @@ import Row from 'src/components/layout/Row'
|
|||
import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg'
|
||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||
import { Checkbox } from '@gnosis.pm/safe-react-components'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { addToken } from 'src/logic/tokens/store/actions/addToken'
|
||||
import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens'
|
||||
import activateTokenForAllSafes from 'src/logic/safe/store/actions/activateTokenForAllSafes'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { List, Set } from 'immutable'
|
||||
|
||||
export const ADD_CUSTOM_TOKEN_ADDRESS_INPUT_TEST_ID = 'add-custom-token-address-input'
|
||||
export const ADD_CUSTOM_TOKEN_SYMBOLS_INPUT_TEST_ID = 'add-custom-token-symbols-input'
|
||||
|
@ -35,20 +41,22 @@ const INITIAL_FORM_STATE = {
|
|||
logoUri: '',
|
||||
}
|
||||
|
||||
const AddCustomToken = (props) => {
|
||||
const {
|
||||
activateTokenForAllSafes,
|
||||
activeTokens,
|
||||
addToken,
|
||||
classes,
|
||||
onClose,
|
||||
parentList,
|
||||
safeAddress,
|
||||
setActiveScreen,
|
||||
tokens,
|
||||
updateActiveTokens,
|
||||
} = props
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
type Props = {
|
||||
activeTokens: List<Token>
|
||||
onClose: () => void
|
||||
parentList: string
|
||||
safeAddress: string
|
||||
setActiveScreen: (screen: string) => void
|
||||
tokens: List<Token>
|
||||
}
|
||||
|
||||
const AddCustomToken = (props: Props): React.ReactElement => {
|
||||
const { activeTokens, onClose, parentList, safeAddress, setActiveScreen, tokens } = props
|
||||
const [formValues, setFormValues] = useState(INITIAL_FORM_STATE)
|
||||
const classes = useStyles()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const handleSubmit = (values) => {
|
||||
const address = checksumAddress(values.address)
|
||||
|
@ -59,12 +67,12 @@ const AddCustomToken = (props) => {
|
|||
name: values.symbol,
|
||||
}
|
||||
|
||||
addToken(token)
|
||||
dispatch(addToken(token))
|
||||
if (values.showForAllSafes) {
|
||||
activateTokenForAllSafes(token.address)
|
||||
dispatch(activateTokenForAllSafes(token.address))
|
||||
} else {
|
||||
const activeTokensAddresses = activeTokens.map(({ address }) => address)
|
||||
updateActiveTokens(safeAddress, activeTokensAddresses.push(token.address))
|
||||
const activeTokensAddresses = Set(activeTokens.map(({ address }) => address))
|
||||
dispatch(updateActiveTokens(safeAddress, activeTokensAddresses.add(token.address)))
|
||||
}
|
||||
|
||||
onClose()
|
||||
|
@ -203,6 +211,4 @@ const AddCustomToken = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const AddCustomTokenComponent = withStyles(styles as any)(AddCustomToken)
|
||||
|
||||
export default AddCustomTokenComponent
|
||||
export default AddCustomToken
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { lg, md } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
title: {
|
||||
padding: `${lg} 0 20px`,
|
||||
fontSize: md,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import MuiList from '@material-ui/core/List'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Search from '@material-ui/icons/Search'
|
||||
import cn from 'classnames'
|
||||
import { Set } from 'immutable'
|
||||
import { List, Set } from 'immutable'
|
||||
import SearchBar from 'material-ui-search-bar'
|
||||
import * as React from 'react'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
|
@ -17,10 +17,15 @@ import Button from 'src/components/layout/Button'
|
|||
import Divider from 'src/components/layout/Divider'
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
import Row from 'src/components/layout/Row'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import updateBlacklistedTokens from 'src/logic/safe/store/actions/updateBlacklistedTokens'
|
||||
import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens'
|
||||
|
||||
export const ADD_CUSTOM_TOKEN_BUTTON_TEST_ID = 'add-custom-token-btn'
|
||||
|
||||
const filterBy = (filter, tokens) =>
|
||||
const filterBy = (filter: string, tokens: List<Token>): List<Token> =>
|
||||
tokens.filter(
|
||||
(token) =>
|
||||
!filter ||
|
||||
|
@ -28,163 +33,128 @@ const filterBy = (filter, tokens) =>
|
|||
token.name.toLowerCase().includes(filter.toLowerCase()),
|
||||
)
|
||||
|
||||
// OPTIMIZATION IDEA (Thanks Andre)
|
||||
// Calculate active tokens on component mount, store it in component state
|
||||
// After user closes modal, dispatch an action so we don't have 100500 actions
|
||||
// And selectors don't recalculate
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
class Tokens extends React.Component<any> {
|
||||
renderCount = 0
|
||||
type Props = {
|
||||
setActiveScreen: (newScreen: string) => void
|
||||
tokens: List<Token>
|
||||
activeTokens: List<Token>
|
||||
blacklistedTokens: Set<string>
|
||||
safeAddress: string
|
||||
}
|
||||
|
||||
state = {
|
||||
filter: '',
|
||||
activeTokensAddresses: Set([]),
|
||||
initialActiveTokensAddresses: Set([]),
|
||||
blacklistedTokensAddresses: Set([]),
|
||||
activeTokensCalculated: false,
|
||||
blacklistedTokensCalculated: false,
|
||||
}
|
||||
export const TokenList = (props: Props): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const { setActiveScreen, tokens, activeTokens, blacklistedTokens, safeAddress } = props
|
||||
const [activeTokensAddresses, setActiveTokensAddresses] = useState(Set(activeTokens.map(({ address }) => address)))
|
||||
const [blacklistedTokensAddresses, setBlacklistedTokensAddresses] = useState<Set<string>>(blacklistedTokens)
|
||||
const [filter, setFilter] = useState('')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
// I moved this logic here because if placed in ComponentDidMount
|
||||
// the user would see Switches switch and this method fires before the component mounts
|
||||
|
||||
if (!prevState.activeTokensCalculated) {
|
||||
const { activeTokens } = nextProps
|
||||
|
||||
return {
|
||||
activeTokensAddresses: Set(activeTokens.map(({ address }) => address)),
|
||||
initialActiveTokensAddresses: Set(activeTokens.map(({ address }) => address)),
|
||||
activeTokensCalculated: true,
|
||||
}
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(updateActiveTokens(safeAddress, activeTokensAddresses))
|
||||
dispatch(updateBlacklistedTokens(safeAddress, blacklistedTokensAddresses))
|
||||
}
|
||||
}, [dispatch, safeAddress, activeTokensAddresses, blacklistedTokensAddresses])
|
||||
|
||||
if (!prevState.blacklistedTokensCalculated) {
|
||||
const { blacklistedTokens } = nextProps
|
||||
|
||||
return {
|
||||
blacklistedTokensAddresses: blacklistedTokens,
|
||||
blacklistedTokensCalculated: true,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
const searchClasses = {
|
||||
input: classes.searchInput,
|
||||
root: classes.searchRoot,
|
||||
iconButton: classes.searchIcon,
|
||||
searchContainer: classes.searchContainer,
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { activeTokensAddresses, blacklistedTokensAddresses } = this.state
|
||||
const { safeAddress, updateActiveTokens, updateBlacklistedTokens } = this.props
|
||||
|
||||
updateActiveTokens(safeAddress, activeTokensAddresses)
|
||||
updateBlacklistedTokens(safeAddress, blacklistedTokensAddresses)
|
||||
}
|
||||
|
||||
onCancelSearch = () => {
|
||||
const onCancelSearch = () => {
|
||||
setFilter('')
|
||||
this.setState(() => ({ filter: '' }))
|
||||
}
|
||||
|
||||
onChangeSearchBar = (value) => {
|
||||
this.setState(() => ({ filter: value }))
|
||||
const onChangeSearchBar = (value: string) => {
|
||||
setFilter(value)
|
||||
}
|
||||
|
||||
onSwitch = (token) => () => {
|
||||
this.setState((prevState: any) => {
|
||||
const activeTokensAddresses = prevState.activeTokensAddresses.has(token.address)
|
||||
? prevState.activeTokensAddresses.remove(token.address)
|
||||
: prevState.activeTokensAddresses.add(token.address)
|
||||
|
||||
let { blacklistedTokensAddresses } = prevState
|
||||
if (activeTokensAddresses.has(token.address)) {
|
||||
blacklistedTokensAddresses = prevState.blacklistedTokensAddresses.remove(token.address)
|
||||
} else if (prevState.initialActiveTokensAddresses.has(token.address)) {
|
||||
blacklistedTokensAddresses = prevState.blacklistedTokensAddresses.add(token.address)
|
||||
}
|
||||
|
||||
return { ...prevState, activeTokensAddresses, blacklistedTokensAddresses }
|
||||
})
|
||||
}
|
||||
|
||||
createItemData = (tokens, activeTokensAddresses) => ({
|
||||
tokens,
|
||||
activeTokensAddresses,
|
||||
onSwitch: this.onSwitch,
|
||||
})
|
||||
|
||||
getItemKey = (index, { tokens }) => {
|
||||
const token = tokens.get(index)
|
||||
|
||||
return token.address
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, setActiveScreen, tokens } = this.props
|
||||
const { activeTokensAddresses, filter } = this.state
|
||||
const searchClasses = {
|
||||
input: classes.searchInput,
|
||||
root: classes.searchRoot,
|
||||
iconButton: classes.searchIcon,
|
||||
searchContainer: classes.searchContainer,
|
||||
const onSwitch = (token: Token) => () => {
|
||||
if (activeTokensAddresses.has(token.address)) {
|
||||
const newTokens = activeTokensAddresses.remove(token.address)
|
||||
setActiveTokensAddresses(newTokens)
|
||||
setBlacklistedTokensAddresses(blacklistedTokensAddresses.add(token.address))
|
||||
} else {
|
||||
setActiveTokensAddresses(activeTokensAddresses.add(token.address))
|
||||
setBlacklistedTokensAddresses(blacklistedTokensAddresses.remove(token.address))
|
||||
}
|
||||
const switchToAddCustomTokenScreen = () => setActiveScreen('addCustomToken')
|
||||
|
||||
const filteredTokens = filterBy(filter, tokens)
|
||||
const itemData = this.createItemData(filteredTokens, activeTokensAddresses)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Block className={classes.root}>
|
||||
<Row align="center" className={cn(classes.padding, classes.actions)}>
|
||||
<Search className={classes.search} />
|
||||
<SearchBar
|
||||
classes={searchClasses}
|
||||
onCancelSearch={this.onCancelSearch}
|
||||
onChange={this.onChangeSearchBar}
|
||||
placeholder="Search by name or symbol"
|
||||
searchIcon={<div />}
|
||||
value={filter}
|
||||
/>
|
||||
<Spacer />
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<Button
|
||||
classes={{ label: classes.addBtnLabel }}
|
||||
className={classes.add}
|
||||
color="primary"
|
||||
onClick={switchToAddCustomTokenScreen}
|
||||
size="small"
|
||||
testId={ADD_CUSTOM_TOKEN_BUTTON_TEST_ID}
|
||||
variant="contained"
|
||||
>
|
||||
+ Add custom token
|
||||
</Button>
|
||||
</Row>
|
||||
<Hairline />
|
||||
</Block>
|
||||
{!tokens.size && (
|
||||
<Block className={classes.progressContainer} justify="center">
|
||||
<CircularProgress />
|
||||
</Block>
|
||||
)}
|
||||
{tokens.size > 0 && (
|
||||
<MuiList className={classes.list}>
|
||||
<FixedSizeList
|
||||
height={413}
|
||||
itemCount={filteredTokens.size}
|
||||
itemData={itemData}
|
||||
itemKey={this.getItemKey}
|
||||
itemSize={51}
|
||||
overscanCount={process.env.NODE_ENV === 'test' ? 100 : 10}
|
||||
width={500}
|
||||
>
|
||||
{TokenRow}
|
||||
</FixedSizeList>
|
||||
</MuiList>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const createItemData = (
|
||||
tokens: List<Token>,
|
||||
activeTokensAddresses: Set<string>,
|
||||
): { tokens: List<Token>; activeTokensAddresses: Set<string>; onSwitch: (token: Token) => void } => {
|
||||
return {
|
||||
tokens,
|
||||
activeTokensAddresses,
|
||||
onSwitch: onSwitch,
|
||||
}
|
||||
}
|
||||
|
||||
const switchToAddCustomTokenScreen = () => setActiveScreen('addCustomToken')
|
||||
|
||||
const getItemKey = (index: number, { tokens }): string => {
|
||||
return tokens.get(index).address
|
||||
}
|
||||
|
||||
const filteredTokens = filterBy(filter, tokens)
|
||||
const itemData = createItemData(filteredTokens, activeTokensAddresses)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Block className={classes.root}>
|
||||
<Row align="center" className={cn(classes.padding, classes.actions)}>
|
||||
<Search className={classes.search} />
|
||||
<SearchBar
|
||||
classes={searchClasses}
|
||||
onCancelSearch={onCancelSearch}
|
||||
onChange={onChangeSearchBar}
|
||||
placeholder="Search by name or symbol"
|
||||
searchIcon={<div />}
|
||||
value={filter}
|
||||
/>
|
||||
<Spacer />
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<Button
|
||||
classes={{ label: classes.addBtnLabel }}
|
||||
className={classes.add}
|
||||
color="primary"
|
||||
onClick={switchToAddCustomTokenScreen}
|
||||
size="small"
|
||||
testId={ADD_CUSTOM_TOKEN_BUTTON_TEST_ID}
|
||||
variant="contained"
|
||||
>
|
||||
+ Add custom token
|
||||
</Button>
|
||||
</Row>
|
||||
<Hairline />
|
||||
</Block>
|
||||
{!tokens.size && (
|
||||
<Block className={classes.progressContainer} justify="center">
|
||||
<CircularProgress />
|
||||
</Block>
|
||||
)}
|
||||
{tokens.size > 0 && (
|
||||
<MuiList className={classes.list}>
|
||||
<FixedSizeList
|
||||
height={413}
|
||||
itemCount={filteredTokens.size}
|
||||
itemData={itemData}
|
||||
itemKey={getItemKey}
|
||||
itemSize={51}
|
||||
overscanCount={process.env.NODE_ENV === 'test' ? 100 : 10}
|
||||
width={500}
|
||||
>
|
||||
{TokenRow}
|
||||
</FixedSizeList>
|
||||
</MuiList>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const TokenComponent = withStyles(styles as any)(Tokens)
|
||||
|
||||
export default TokenComponent
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { border, md, mediumFontSize, secondaryText, sm, xs } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
root: {
|
||||
minHeight: '52px',
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { lg, md } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
heading: {
|
||||
padding: `${md} ${lg}`,
|
||||
justifyContent: 'space-between',
|
||||
|
|
|
@ -42,7 +42,9 @@ const getTokenPriceInCurrency = (
|
|||
|
||||
export interface BalanceData {
|
||||
asset: { name: string; logoUri: string; address: string; symbol: string }
|
||||
assetOrder: string
|
||||
balance: string
|
||||
balanceOrder: number
|
||||
fixed: boolean
|
||||
value: string
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import safe, { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe'
|
|||
import transactions, { TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/transactions'
|
||||
import { NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/OpenSea'
|
||||
import { SafeReducerMap } from 'src/routes/safe/store/reducer/types/safe'
|
||||
import allTransactions, { TRANSACTIONS, TransactionsState } from '../logic/safe/store/reducer/allTransactions'
|
||||
import allTransactions, { TRANSACTIONS, TransactionsState } from 'src/logic/safe/store/reducer/allTransactions'
|
||||
|
||||
export const history = createHashHistory()
|
||||
|
||||
|
|
Loading…
Reference in New Issue