WA-232 Storing in localStorage active tokens

This commit is contained in:
apanizo 2018-07-11 13:01:58 +02:00
parent be9bfe0df9
commit 84057d03ee
10 changed files with 97 additions and 73 deletions

View File

@ -7,11 +7,11 @@ import GnoSafe from './Safe'
type Props = SelectorProps
const Layout = ({
safe, tokens, provider, userAddress,
safe, activeTokens, provider, userAddress,
}: Props) => (
<React.Fragment>
{ safe
? <GnoSafe safe={safe} tokens={tokens} userAddress={userAddress} />
? <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
: <NoSafe provider={provider} text="Not found safe" />
}
</React.Fragment>

View File

@ -30,7 +30,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={undefined}
provider="METAMASK"
tokens={Map()}
activeTokens={Map()}
fetchBalance={() => {}}
/>
))
@ -39,7 +39,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={undefined}
provider=""
tokens={Map()}
activeTokens={Map()}
fetchBalance={() => {}}
/>
))
@ -51,7 +51,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={safe}
provider="METAMASK"
tokens={Map().set('ETH', ethBalance)}
activeTokens={Map().set('ETH', ethBalance)}
fetchBalance={() => {}}
/>
)
@ -64,7 +64,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={safe}
provider="METAMASK"
tokens={Map().set('ETH', ethBalance)}
activeTokens={Map().set('ETH', ethBalance)}
fetchBalance={() => {}}
/>
)

View File

@ -16,7 +16,9 @@ const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
class SafeView extends React.PureComponent<Props> {
componentDidMount() {
this.intervalId = setInterval(() => {
const { safe, fetchTokens, fetchSafe } = this.props
const {
safe, fetchTokens, fetchSafe,
} = this.props
if (!safe) {
return
}
@ -45,13 +47,13 @@ class SafeView extends React.PureComponent<Props> {
render() {
const {
safe, provider, tokens, granted, userAddress,
safe, provider, activeTokens, granted, userAddress,
} = this.props
return (
<Page>
{ granted
? <Layout tokens={tokens} provider={provider} safe={safe} userAddress={userAddress} />
? <Layout activeTokens={activeTokens} provider={provider} safe={safe} userAddress={userAddress} />
: <NoRights />
}
</Page>

View File

@ -7,13 +7,13 @@ import { type Safe } from '~/routes/safe/store/model/safe'
import { type Owner } from '~/routes/safe/store/model/owner'
import { type GlobalState } from '~/store/index'
import { sameAddress } from '~/wallets/ethAddresses'
import { tokensSelector } from '~/routes/tokens/store/selectors'
import { activeTokensSelector } from '~/routes/tokens/store/selectors'
import { type Token } from '~/routes/tokens/store/model/token'
export type SelectorProps = {
safe: SafeSelectorProps,
provider: string,
tokens: Map<string, Token>,
activeTokens: Map<string, Token>,
userAddress: string,
}
@ -41,7 +41,7 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
export default createStructuredSelector({
safe: safeSelector,
provider: providerNameSelector,
tokens: tokensSelector,
activeTokens: activeTokensSelector,
granted: grantedSelector,
userAddress: userAccountSelector,
})

View File

@ -44,7 +44,7 @@ const styles = () => ({
class TokenComponent extends React.Component<Props, State> {
state = {
checked: true,
checked: this.props.token.get('status'),
}
// onRemoveClick = () => this.props.onRemoveToken(this.props.token)

View File

@ -9,6 +9,7 @@ const disableToken = createAction(
(safeAddress: string, token: Token) => ({
safeAddress,
symbol: token.get('symbol'),
address: token.get('address'),
}),
)

View File

@ -9,6 +9,7 @@ const enableToken = createAction(
(safeAddress: string, token: Token) => ({
safeAddress,
symbol: token.get('symbol'),
address: token.get('address'),
}),
)

View File

@ -1,16 +1,17 @@
// @flow
import { Map } from 'immutable'
import { List, Map } from 'immutable'
import contract from 'truffle-contract'
import type { Dispatch as ReduxDispatch } from 'redux'
import StandardToken from '@gnosis.pm/util-contracts/build/contracts/StandardToken.json'
import { getBalanceInEtherOf, getWeb3 } from '~/wallets/getWeb3'
import { getWeb3 } from '~/wallets/getWeb3'
import { type GlobalState } from '~/store/index'
import { makeToken, type Token, type TokenProps } from '~/routes/tokens/store/model/token'
import logo from '~/assets/icons/icon_etherTokens.svg'
import { ensureOnce } from '~/utils/singleton'
import { getTokens } from '~/utils/localStorage/tokens'
import { getSafeEthToken } from '~/utils/tokens'
import { enhancedFetch } from '~/utils/fetch'
import addTokens from './addTokens'
const createStandardTokenContract = async () => {
const web3 = getWeb3()
const erc20Token = await contract(StandardToken)
@ -29,49 +30,33 @@ export const calculateBalanceOf = async (tokenAddress: string, address: string,
.catch(() => '0')
}
export const fetchTokens = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
const balance = await getBalanceInEtherOf(safeAddress)
const ethBalance = makeToken({
address: '0',
name: 'Ether',
symbol: 'ETH',
decimals: 18,
logoUrl: logo,
funds: balance,
})
export const fetchTokens = (safeAddress: string) =>
async (dispatch: ReduxDispatch<GlobalState>) => {
const tokens: List<string> = getTokens(safeAddress)
const ethBalance = await getSafeEthToken(safeAddress)
const header = new Headers({
'Access-Control-Allow-Origin': '*',
})
const url = 'https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/'
const errMsg = 'Error querying safe balances'
const json = await enhancedFetch(url, errMsg)
const sentData = {
mode: 'cors',
header,
try {
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
const funds = await calculateBalanceOf(item.address, safeAddress, item.decimals)
const status = tokens.includes(item.address)
return makeToken({ ...item, status, funds })
}))
const balances: Map<string, Token> = Map().withMutations((map) => {
balancesRecords.forEach(record => map.set(record.get('symbol'), record))
map.set('ETH', ethBalance)
})
return dispatch(addTokens(safeAddress, balances))
} catch (err) {
// eslint-disable-next-line
console.log("Error fetching token balances... " + err)
return Promise.resolve()
}
}
const response = await fetch('https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/', sentData)
if (!response.ok) {
throw new Error('Error querying safe balances')
}
const json = await response.json()
try {
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
const funds = await calculateBalanceOf(item.address, safeAddress, item.decimals)
return makeToken({ ...item, funds })
}))
const balances: Map<string, Token> = Map().withMutations((map) => {
balancesRecords.forEach(record => map.set(record.get('symbol'), record))
map.set('ETH', ethBalance)
})
return dispatch(addTokens(safeAddress, balances))
} catch (err) {
// eslint-disable-next-line
console.log("Error fetching token balances...")
return Promise.resolve()
}
}

View File

@ -1,26 +1,50 @@
// @flow
import { Map } from 'immutable'
import { List, Map } from 'immutable'
import { handleActions, type ActionType } from 'redux-actions'
import addTokens, { ADD_TOKENS } from '~/routes/tokens/store/actions/addTokens'
import { type Token } from '~/routes/tokens/store/model/token'
import disableToken, { DISABLE_TOKEN } from '~/routes/tokens/store/actions/disableToken'
import enableToken, { ENABLE_TOKEN } from '~/routes/tokens/store/actions/enableToken'
import { setTokens, getTokens } from '~/utils/localStorage/tokens'
import { ensureOnce } from '~/utils/singleton'
import { calculateActiveErc20TokensFrom } from '~/utils/tokens'
export const TOKEN_REDUCER_ID = 'tokens'
export type State = Map<string, Map<string, Token>>
const setTokensOnce = ensureOnce(setTokens)
export default handleActions({
[ADD_TOKENS]: (state: State, action: ActionType<typeof addTokens>): State =>
state.update(action.payload.safeAddress, (prevSafe: Map<string, Token>) => {
[ADD_TOKENS]: (state: State, action: ActionType<typeof addTokens>): State => {
const { safeAddress, tokens } = action.payload
const activeAddresses: List<Token> = calculateActiveErc20TokensFrom(tokens.toList())
setTokensOnce(safeAddress, activeAddresses)
return state.update(safeAddress, (prevSafe: Map<string, Token>) => {
if (!prevSafe) {
return action.payload.tokens
return tokens
}
return prevSafe.equals(action.payload.tokens) ? prevSafe : action.payload.tokens
}),
[DISABLE_TOKEN]: (state: State, action: ActionType<typeof disableToken>): State =>
state.setIn([action.payload.safeAddress, action.payload.symbol, 'status'], false),
[ENABLE_TOKEN]: (state: State, action: ActionType<typeof enableToken>): State =>
state.setIn([action.payload.safeAddress, action.payload.symbol, 'status'], true),
return prevSafe.equals(tokens) ? prevSafe : tokens
})
},
[DISABLE_TOKEN]: (state: State, action: ActionType<typeof disableToken>): State => {
const { address, safeAddress, symbol } = action.payload
const activeTokens = getTokens(safeAddress)
const index = activeTokens.indexOf(address)
setTokens(safeAddress, activeTokens.delete(index))
return state.setIn([safeAddress, symbol, 'status'], false)
},
[ENABLE_TOKEN]: (state: State, action: ActionType<typeof enableToken>): State => {
const { address, safeAddress, symbol } = action.payload
const activeTokens = getTokens(safeAddress)
setTokens(safeAddress, activeTokens.push(address))
return state.setIn([safeAddress, symbol, 'status'], true)
},
}, Map())

View File

@ -5,24 +5,30 @@ import { safeParamAddressSelector, type RouterProps } from '~/routes/safe/store/
import { type GlobalState } from '~/store'
import { TOKEN_REDUCER_ID } from '~/routes/tokens/store/reducer/tokens'
import { type Token } from '~/routes/tokens/store/model/token'
import { calculateActiveErc20TokensFrom } from '~/utils/tokens'
const balancesSelector = (state: GlobalState) => state[TOKEN_REDUCER_ID]
export const tokensSelector: Selector<GlobalState, RouterProps, Map<string, Token>> = createSelector(
balancesSelector,
safeParamAddressSelector,
(balances: Map<string, Map<string, Token>>, address: string) => {
(tokens: Map<string, Map<string, Token>>, address: string) => {
if (!address) {
return Map()
}
return balances.get(address) || Map()
return tokens.get(address) || Map()
},
)
export const tokenListSelector = createSelector(
tokensSelector,
(balances: Map<string, Token>) => balances.toList(),
(tokens: Map<string, Token>) => tokens.toList(),
)
export const activeTokensSelector = createSelector(
tokenListSelector,
(tokens: List<Token>) => tokens.filter((token: Token) => token.get('status')),
)
export const tokenAddressesSelector = createSelector(
@ -34,3 +40,8 @@ export const tokenAddressesSelector = createSelector(
return addresses
},
)
export const activeTokenAddressesSelector = createSelector(
tokenListSelector,
(balances: List<Token>) => calculateActiveErc20TokensFrom(balances),
)