diff --git a/package.json b/package.json index 5aceead4..8d62c911 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "safe-react", - "version": "2.11.1", + "version": "2.12.0", "description": "Allowing crypto users manage funds in a safer way", "website": "https://github.com/gnosis/safe-react#readme", "bugs": { diff --git a/src/logic/safe/store/actions/updateActiveAssets.ts b/src/logic/safe/store/actions/updateActiveAssets.ts index 4f107c17..80be2139 100644 --- a/src/logic/safe/store/actions/updateActiveAssets.ts +++ b/src/logic/safe/store/actions/updateActiveAssets.ts @@ -1,17 +1,9 @@ -import updateSafe from './updateSafe' +import { Set } from 'immutable' +import updateAssetsList from './updateAssetsList' +import { Dispatch } from 'src/logic/safe/store/actions/types.d' -// 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 -// const generateMatchProps = (safeAddress: string) => ({ -// match: { -// params: { -// [SAFE_PARAM_ADDRESS]: safeAddress, -// }, -// }, -// }) - -const updateActiveAssets = (safeAddress, activeAssets) => async (dispatch) => { - dispatch(updateSafe({ address: safeAddress, activeAssets })) +const updateActiveAssets = (safeAddress: string, activeAssets: Set) => (dispatch: Dispatch): void => { + dispatch(updateAssetsList({ safeAddress, activeAssets })) } export default updateActiveAssets diff --git a/src/logic/safe/store/actions/updateActiveTokens.ts b/src/logic/safe/store/actions/updateActiveTokens.ts index 851fef1f..012836c9 100644 --- a/src/logic/safe/store/actions/updateActiveTokens.ts +++ b/src/logic/safe/store/actions/updateActiveTokens.ts @@ -1,6 +1,6 @@ -import updateSafe from './updateSafe' import { Set } from 'immutable' -import { Dispatch } from 'redux' +import updateTokensList from './updateTokensList' +import { Dispatch } from 'src/logic/safe/store/actions/types.d' // 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 @@ -13,7 +13,7 @@ import { Dispatch } from 'redux' // }) const updateActiveTokens = (safeAddress: string, activeTokens: Set) => (dispatch: Dispatch): void => { - dispatch(updateSafe({ address: safeAddress, activeTokens })) + dispatch(updateTokensList({ safeAddress, activeTokens })) } export default updateActiveTokens diff --git a/src/logic/safe/store/actions/updateAssetsList.ts b/src/logic/safe/store/actions/updateAssetsList.ts new file mode 100644 index 00000000..0f098e28 --- /dev/null +++ b/src/logic/safe/store/actions/updateAssetsList.ts @@ -0,0 +1,7 @@ +import { createAction } from 'redux-actions' + +export const UPDATE_ASSETS_LIST = 'UPDATE_ASSETS_LIST' + +const updateAssetsList = createAction(UPDATE_ASSETS_LIST) + +export default updateAssetsList diff --git a/src/logic/safe/store/actions/updateBlacklistedAssets.ts b/src/logic/safe/store/actions/updateBlacklistedAssets.ts index 76e28b21..8b52bbfd 100644 --- a/src/logic/safe/store/actions/updateBlacklistedAssets.ts +++ b/src/logic/safe/store/actions/updateBlacklistedAssets.ts @@ -1,7 +1,9 @@ -import updateSafe from './updateSafe' +import { Set } from 'immutable' +import updateAssetsList from './updateAssetsList' +import { Dispatch } from 'src/logic/safe/store/actions/types.d' -const updateBlacklistedAssets = (safeAddress, blacklistedAssets) => async (dispatch) => { - dispatch(updateSafe({ address: safeAddress, blacklistedAssets })) +const updateBlacklistedAssets = (safeAddress: string, blacklistedAssets: Set) => (dispatch: Dispatch): void => { + dispatch(updateAssetsList({ safeAddress, blacklistedAssets })) } export default updateBlacklistedAssets diff --git a/src/logic/safe/store/actions/updateBlacklistedTokens.ts b/src/logic/safe/store/actions/updateBlacklistedTokens.ts index b1b1039e..e81293b9 100644 --- a/src/logic/safe/store/actions/updateBlacklistedTokens.ts +++ b/src/logic/safe/store/actions/updateBlacklistedTokens.ts @@ -1,9 +1,9 @@ -import updateSafe from './updateSafe' -import { Dispatch } from 'redux' import { Set } from 'immutable' +import updateTokensList from './updateTokensList' +import { Dispatch } from 'src/logic/safe/store/actions/types.d' const updateBlacklistedTokens = (safeAddress: string, blacklistedTokens: Set) => (dispatch: Dispatch): void => { - dispatch(updateSafe({ address: safeAddress, blacklistedTokens })) + dispatch(updateTokensList({ safeAddress, blacklistedTokens })) } export default updateBlacklistedTokens diff --git a/src/logic/safe/store/actions/updateTokensList.ts b/src/logic/safe/store/actions/updateTokensList.ts new file mode 100644 index 00000000..91123bd6 --- /dev/null +++ b/src/logic/safe/store/actions/updateTokensList.ts @@ -0,0 +1,7 @@ +import { createAction } from 'redux-actions' + +export const UPDATE_TOKENS_LIST = 'UPDATE_TOKENS_LIST' + +const updateTokenList = createAction(UPDATE_TOKENS_LIST) + +export default updateTokenList diff --git a/src/logic/safe/store/middleware/safeStorage.ts b/src/logic/safe/store/middleware/safeStorage.ts index f37ef047..ee9152c2 100644 --- a/src/logic/safe/store/middleware/safeStorage.ts +++ b/src/logic/safe/store/middleware/safeStorage.ts @@ -11,6 +11,8 @@ import { REMOVE_SAFE_OWNER } from 'src/logic/safe/store/actions/removeSafeOwner' import { REPLACE_SAFE_OWNER } from 'src/logic/safe/store/actions/replaceSafeOwner' import { SET_DEFAULT_SAFE } from 'src/logic/safe/store/actions/setDefaultSafe' import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe' +import { UPDATE_TOKENS_LIST } from 'src/logic/safe/store/actions/updateTokensList' +import { UPDATE_ASSETS_LIST } from 'src/logic/safe/store/actions/updateAssetsList' import { getActiveTokensAddressesForAllSafes, safesMapSelector } from 'src/logic/safe/store/selectors' import { checksumAddress } from 'src/utils/checksumAddress' import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' @@ -31,6 +33,8 @@ const watchedActions = [ REPLACE_SAFE_OWNER, EDIT_SAFE_OWNER, ACTIVATE_TOKEN_FOR_ALL_SAFES, + UPDATE_TOKENS_LIST, + UPDATE_ASSETS_LIST, SET_DEFAULT_SAFE, ] diff --git a/src/logic/safe/store/reducer/safe.ts b/src/logic/safe/store/reducer/safe.ts index f6d094bf..3d170f8e 100644 --- a/src/logic/safe/store/reducer/safe.ts +++ b/src/logic/safe/store/reducer/safe.ts @@ -11,11 +11,14 @@ import { REPLACE_SAFE_OWNER } from 'src/logic/safe/store/actions/replaceSafeOwne import { SET_DEFAULT_SAFE } from 'src/logic/safe/store/actions/setDefaultSafe' import { SET_LATEST_MASTER_CONTRACT_VERSION } from 'src/logic/safe/store/actions/setLatestMasterContractVersion' import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe' +import { UPDATE_TOKENS_LIST } from 'src/logic/safe/store/actions/updateTokensList' +import { UPDATE_ASSETS_LIST } from 'src/logic/safe/store/actions/updateAssetsList' import { makeOwner } from 'src/logic/safe/store/models/owner' import makeSafe, { SafeRecordProps } from 'src/logic/safe/store/models/safe' import { checksumAddress } from 'src/utils/checksumAddress' import { SafeReducerMap } from 'src/routes/safe/store/reducer/types/safe' import { ADD_OR_UPDATE_SAFE } from 'src/logic/safe/store/actions/addOrUpdateSafe' +import { sameAddress } from 'src/logic/wallets/ethAddresses' export const SAFE_REDUCER_ID = 'safes' export const DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED' @@ -51,10 +54,10 @@ const updateSafeProps = (prevSafe, safe) => { // We check each safe property sent in action.payload safeProperties.forEach((key) => { if (safe[key] && typeof safe[key] === 'object') { - if (safe[key].length) { + if (safe[key].length >= 0) { // If type is array we update the array record.update(key, () => safe[key]) - } else if (safe[key].size) { + } else if (safe[key].size >= 0) { // If type is Immutable List we replace current List // If type is Object we do a merge List.isList(safe[key]) @@ -130,6 +133,14 @@ export default handleActions( [ADD_SAFE_OWNER]: (state: SafeReducerMap, action) => { const { ownerAddress, ownerName, safeAddress } = action.payload + const addressFound = state + .getIn(['safes', safeAddress]) + .owners.find((owner) => sameAddress(owner.address, ownerAddress)) + + if (addressFound) { + return state + } + return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.merge({ owners: prevSafe.owners.push(makeOwner({ address: ownerAddress, name: ownerName })), @@ -167,6 +178,24 @@ export default handleActions( return prevSafe.merge({ owners: updatedOwners }) }) }, + [UPDATE_TOKENS_LIST]: (state: SafeReducerMap, action) => { + // Only activeTokens or blackListedTokens is required + const { safeAddress, activeTokens, blacklistedTokens } = action.payload + + const key = activeTokens ? 'activeTokens' : 'blacklistedTokens' + const list = activeTokens ?? blacklistedTokens + + return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.set(key, list)) + }, + [UPDATE_ASSETS_LIST]: (state: SafeReducerMap, action) => { + // Only activeAssets or blackListedAssets is required + const { safeAddress, activeAssets, blacklistedAssets } = action.payload + + const key = activeAssets ? 'activeAssets' : 'blacklistedAssets' + const list = activeAssets ?? blacklistedAssets + + return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.set(key, list)) + }, [SET_DEFAULT_SAFE]: (state: SafeReducerMap, action) => state.set('defaultSafe', action.payload), [SET_LATEST_MASTER_CONTRACT_VERSION]: (state: SafeReducerMap, action) => state.set('latestMasterContractVersion', action.payload), diff --git a/src/logic/safe/store/tests/safe.balances.test.ts b/src/logic/safe/store/tests/safe.balances.test.ts index 619df7aa..4238d4c3 100644 --- a/src/logic/safe/store/tests/safe.balances.test.ts +++ b/src/logic/safe/store/tests/safe.balances.test.ts @@ -30,15 +30,13 @@ describe('Feature > Balances', () => { const expectedResult = '100' // when - store.dispatch(updateActiveTokens(safeAddress, Set([token.address]))) store.dispatch(updateSafe({ address: safeAddress, balances })) + store.dispatch(updateActiveTokens(safeAddress, Set([token.address]))) const safe = safesMapSelector(store.getState()).get(safeAddress) - //@ts-ignore - const balanceResult = safe.get('balances').get(token.address) - //@ts-ignore - const activeTokens = safe.get('activeTokens') - const tokenIsActive = activeTokens.has(token.address) + const balanceResult = safe?.get('balances').get(token.address) + const activeTokens = safe?.get('activeTokens') + const tokenIsActive = activeTokens?.has(token.address) // then expect(balanceResult).toBe(expectedResult) @@ -53,8 +51,7 @@ describe('Feature > Balances', () => { // when store.dispatch(updateSafe({ address: safeAddress, ethBalance: etherAmount })) const safe = safesMapSelector(store.getState()).get(safeAddress) - //@ts-ignore - const balanceResult = safe.get('ethBalance') + const balanceResult = safe?.get('ethBalance') // then expect(balanceResult).toBe(expectedResult) diff --git a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx index 3a3ccb6c..1e87da0a 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx +++ b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx @@ -4,7 +4,7 @@ import { makeStyles } from '@material-ui/core/styles' import Search from '@material-ui/icons/Search' import cn from 'classnames' import SearchBar from 'material-ui-search-bar' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { FixedSizeList } from 'react-window' @@ -55,11 +55,6 @@ const AssetsList = (props) => { const [blacklistedAssetsAddresses, setBlacklistedAssetsAddresses] = useState(blacklistedAssets) const nftAssetsList = useSelector(nftAssetsListSelector) - useEffect(() => { - dispatch(updateActiveAssets(safeAddress, activeAssetsAddresses)) - dispatch(updateBlacklistedAssets(safeAddress, blacklistedAssetsAddresses)) - }, [activeAssetsAddresses, blacklistedAssetsAddresses, dispatch, safeAddress]) - const onCancelSearch = () => { setFilterValue('') } @@ -73,19 +68,22 @@ const AssetsList = (props) => { } const onSwitch = (asset) => () => { - const { address } = asset - const activeAssetsAddressesResult = activeAssetsAddresses.contains(address) - ? activeAssetsAddresses.remove(address) - : activeAssetsAddresses.add(address) - const blacklistedAssetsAddressesResult = activeAssetsAddresses.has(address) - ? blacklistedAssetsAddresses.add(address) - : blacklistedAssetsAddresses.remove(address) - setActiveAssetsAddresses(activeAssetsAddressesResult) - setBlacklistedAssetsAddresses(blacklistedAssetsAddressesResult) - return { - activeAssetsAddresses: activeAssetsAddressesResult, - blacklistedAssetsAddresses: blacklistedAssetsAddressesResult, + let newActiveAssetsAddresses + let newBlacklistedAssetsAddresses + if (activeAssetsAddresses.has(asset.address)) { + newActiveAssetsAddresses = activeAssetsAddresses.delete(asset.address) + newBlacklistedAssetsAddresses = blacklistedAssetsAddresses.add(asset.address) + } else { + newActiveAssetsAddresses = activeAssetsAddresses.add(asset.address) + newBlacklistedAssetsAddresses = blacklistedAssetsAddresses.delete(asset.address) } + + // Set local state + setActiveAssetsAddresses(newActiveAssetsAddresses) + setBlacklistedAssetsAddresses(newBlacklistedAssetsAddresses) + // Dispatch to global state + dispatch(updateActiveAssets(safeAddress, newActiveAssetsAddresses)) + dispatch(updateBlacklistedAssets(safeAddress, newBlacklistedAssetsAddresses)) } const createItemData = (assetsList) => { diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx index 73629ad8..999dd941 100644 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx +++ b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx @@ -5,7 +5,7 @@ import Search from '@material-ui/icons/Search' import cn from 'classnames' import { List, Set } from 'immutable' import SearchBar from 'material-ui-search-bar' -import * as React from 'react' +import React, { useState } from 'react' import { FixedSizeList } from 'react-window' import TokenRow from './TokenRow' @@ -17,7 +17,6 @@ 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' @@ -51,13 +50,6 @@ export const TokenList = (props: Props): React.ReactElement => { const [filter, setFilter] = useState('') const dispatch = useDispatch() - useEffect(() => { - return () => { - dispatch(updateActiveTokens(safeAddress, activeTokensAddresses)) - dispatch(updateBlacklistedTokens(safeAddress, blacklistedTokensAddresses)) - } - }, [dispatch, safeAddress, activeTokensAddresses, blacklistedTokensAddresses]) - const searchClasses = { input: classes.searchInput, root: classes.searchRoot, @@ -75,14 +67,22 @@ export const TokenList = (props: Props): React.ReactElement => { } const onSwitch = (token: Token) => () => { + let newActiveTokensAddresses + let newBlacklistedTokensAddresses if (activeTokensAddresses.has(token.address)) { - const newTokens = activeTokensAddresses.remove(token.address) - setActiveTokensAddresses(newTokens) - setBlacklistedTokensAddresses(blacklistedTokensAddresses.add(token.address)) + newActiveTokensAddresses = activeTokensAddresses.delete(token.address) + newBlacklistedTokensAddresses = blacklistedTokensAddresses.add(token.address) } else { - setActiveTokensAddresses(activeTokensAddresses.add(token.address)) - setBlacklistedTokensAddresses(blacklistedTokensAddresses.remove(token.address)) + newActiveTokensAddresses = activeTokensAddresses.add(token.address) + newBlacklistedTokensAddresses = blacklistedTokensAddresses.delete(token.address) } + + // Set local state + setActiveTokensAddresses(newActiveTokensAddresses) + setBlacklistedTokensAddresses(newBlacklistedTokensAddresses) + // Dispatch to global state + dispatch(updateActiveTokens(safeAddress, newActiveTokensAddresses)) + dispatch(updateBlacklistedTokens(safeAddress, newBlacklistedTokensAddresses)) } const createItemData = (