diff --git a/src/logic/currencyValues/store/actions/fetchCurrencySelectedValue.js b/src/logic/currencyValues/store/actions/fetchCurrencyRate.js similarity index 57% rename from src/logic/currencyValues/store/actions/fetchCurrencySelectedValue.js rename to src/logic/currencyValues/store/actions/fetchCurrencyRate.js index c4129f9a..13785f16 100644 --- a/src/logic/currencyValues/store/actions/fetchCurrencySelectedValue.js +++ b/src/logic/currencyValues/store/actions/fetchCurrencyRate.js @@ -7,15 +7,15 @@ import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currenc import type { GlobalState } from '~/store' // eslint-disable-next-line max-len -const fetchCurrencySelectedValue = (currencyValueSelected: $Keys) => async ( +const fetchCurrencyRate = (safeAddress: string, selectedCurrency: $Keys) => async ( dispatch: ReduxDispatch, ) => { - if (AVAILABLE_CURRENCIES.USD === currencyValueSelected) { - return dispatch(setCurrencyRate('1')) + if (AVAILABLE_CURRENCIES.USD === selectedCurrency) { + return dispatch(setCurrencyRate(safeAddress, 1)) } - const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, currencyValueSelected) - dispatch(setCurrencyRate(selectedCurrencyRateInBaseCurrency)) + const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, selectedCurrency) + dispatch(setCurrencyRate(safeAddress, selectedCurrencyRateInBaseCurrency)) } -export default fetchCurrencySelectedValue +export default fetchCurrencyRate diff --git a/src/logic/currencyValues/store/actions/fetchCurrencyValues.js b/src/logic/currencyValues/store/actions/fetchCurrencyValues.js index d71b18c0..8eaa60c6 100644 --- a/src/logic/currencyValues/store/actions/fetchCurrencyValues.js +++ b/src/logic/currencyValues/store/actions/fetchCurrencyValues.js @@ -1,36 +1,42 @@ // @flow +import { List } from 'immutable' import { batch } from 'react-redux' import type { Dispatch as ReduxDispatch } from 'redux' -import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue' -import { CURRENCY_SELECTED_KEY } from '~/logic/currencyValues/store/actions/saveCurrencySelected' +import fetchCurrencyRate from '~/logic/currencyValues/store/actions/fetchCurrencyRate' +import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances' import { setCurrencyRate } from '~/logic/currencyValues/store/actions/setCurrencyRate' -import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected' +import { setSelectedCurrency } from '~/logic/currencyValues/store/actions/setSelectedCurrency' import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues' +import { loadCurrencyValues } from '~/logic/currencyValues/store/utils/currencyValuesStorage' +import fetchSafeTokens from '~/logic/tokens/store/actions/fetchSafeTokens' import type { GlobalState } from '~/store' -import { loadFromStorage } from '~/utils/storage' -export const fetchCurrencyValues = () => async (dispatch: ReduxDispatch) => { +export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: ReduxDispatch) => { try { - const currencyStored = await loadFromStorage(CURRENCY_SELECTED_KEY) - - if (!currencyStored) { + const storedCurrencies = await loadCurrencyValues() + const storedCurrency = storedCurrencies[safeAddress] + if (!storedCurrency) { return batch(() => { - dispatch(setCurrencySelected(AVAILABLE_CURRENCIES.USD)) - dispatch(setCurrencyRate(1)) + dispatch(setCurrencyBalances(safeAddress, List([]))) + dispatch(setSelectedCurrency(safeAddress, AVAILABLE_CURRENCIES.USD)) + dispatch(setCurrencyRate(safeAddress, 1)) }) } - - const { currencyValueSelected } = currencyStored - - batch(() => { - dispatch(setCurrencySelected(currencyValueSelected)) - dispatch(fetchCurrencySelectedValue(currencyValueSelected)) + // Loads the stored state on redux + Object.entries(storedCurrencies).forEach((kv) => { + const safeAddr = kv[0] + const value = kv[1] + const { currencyRate, selectedCurrency } = value + batch(() => { + dispatch(setSelectedCurrency(safeAddr, selectedCurrency)) + dispatch(setCurrencyRate(safeAddr, currencyRate)) + dispatch(fetchCurrencyRate(safeAddr, selectedCurrency)) + dispatch(fetchSafeTokens(safeAddress)) + }) }) } catch (err) { - console.error('Error fetching tokens price list', err) + console.error('Error fetching currency values', err) } return Promise.resolve() } - -export default fetchCurrencyValues diff --git a/src/logic/currencyValues/store/actions/saveCurrencySelected.js b/src/logic/currencyValues/store/actions/saveCurrencySelected.js deleted file mode 100644 index 1f1a8774..00000000 --- a/src/logic/currencyValues/store/actions/saveCurrencySelected.js +++ /dev/null @@ -1,19 +0,0 @@ -// @flow - -import { Dispatch as ReduxDispatch } from 'redux' - -import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected' -import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues' -import type { GlobalState } from '~/store' -import { saveToStorage } from '~/utils/storage' - -export const CURRENCY_SELECTED_KEY = 'CURRENCY_SELECTED_KEY' - -const saveCurrencySelected = (currencySelected: AVAILABLE_CURRENCIES) => async ( - dispatch: ReduxDispatch, -) => { - await saveToStorage(CURRENCY_SELECTED_KEY, { currencyValueSelected: currencySelected }) - dispatch(setCurrencySelected(currencySelected)) -} - -export default saveCurrencySelected diff --git a/src/logic/currencyValues/store/actions/setCurrencyBalances.js b/src/logic/currencyValues/store/actions/setCurrencyBalances.js index 7fa52c9d..21219906 100644 --- a/src/logic/currencyValues/store/actions/setCurrencyBalances.js +++ b/src/logic/currencyValues/store/actions/setCurrencyBalances.js @@ -1,5 +1,4 @@ // @flow -import { Map } from 'immutable' import { createAction } from 'redux-actions' import type { CurrencyValues, CurrencyValuesProps } from '~/logic/currencyValues/store/model/currencyValues' @@ -9,5 +8,8 @@ export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES' // eslint-disable-next-line max-len export const setCurrencyBalances = createAction( SET_CURRENCY_BALANCES, - (currencyBalances: Map): CurrencyValuesProps => ({ currencyBalances }), + (safeAddress: string, currencyBalances: List): CurrencyValuesProps => ({ + safeAddress, + currencyBalances, + }), ) diff --git a/src/logic/currencyValues/store/actions/setCurrencyRate.js b/src/logic/currencyValues/store/actions/setCurrencyRate.js index c0cee64c..985daff9 100644 --- a/src/logic/currencyValues/store/actions/setCurrencyRate.js +++ b/src/logic/currencyValues/store/actions/setCurrencyRate.js @@ -8,5 +8,5 @@ export const SET_CURRENCY_RATE = 'SET_CURRENCY_RATE' // eslint-disable-next-line max-len export const setCurrencyRate = createAction( SET_CURRENCY_RATE, - (currencyRate: string): CurrencyValuesProps => ({ currencyRate }), + (safeAddress: string, currencyRate: string): CurrencyValuesProps => ({ safeAddress, currencyRate }), ) diff --git a/src/logic/currencyValues/store/actions/setCurrencySelected.js b/src/logic/currencyValues/store/actions/setSelectedCurrency.js similarity index 63% rename from src/logic/currencyValues/store/actions/setCurrencySelected.js rename to src/logic/currencyValues/store/actions/setSelectedCurrency.js index be0f1c2b..cda7faef 100644 --- a/src/logic/currencyValues/store/actions/setCurrencySelected.js +++ b/src/logic/currencyValues/store/actions/setSelectedCurrency.js @@ -7,7 +7,10 @@ import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currenc export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY' // eslint-disable-next-line max-len -export const setCurrencySelected = createAction( +export const setSelectedCurrency = createAction( SET_CURRENT_CURRENCY, - (currencyValueSelected: $Keys): CurrencyValuesProps => ({ currencyValueSelected }), + (safeAddress: string, selectedCurrency: $Keys): CurrencyValuesProps => ({ + safeAddress, + selectedCurrency, + }), ) diff --git a/src/logic/currencyValues/store/middleware/index.js b/src/logic/currencyValues/store/middleware/index.js new file mode 100644 index 00000000..32d91226 --- /dev/null +++ b/src/logic/currencyValues/store/middleware/index.js @@ -0,0 +1,50 @@ +// @flow +import { Action, Store } from 'redux' + +import fetchCurrencyRate from '~/logic/currencyValues/store/actions/fetchCurrencyRate' +import { SET_CURRENCY_BALANCES } from '~/logic/currencyValues/store/actions/setCurrencyBalances' +import { SET_CURRENCY_RATE } from '~/logic/currencyValues/store/actions/setCurrencyRate' +import { SET_CURRENT_CURRENCY } from '~/logic/currencyValues/store/actions/setSelectedCurrency' +import { currencyValuesSelector } from '~/logic/currencyValues/store/selectors' +import { saveCurrencyValues } from '~/logic/currencyValues/store/utils/currencyValuesStorage' +import type { GlobalState } from '~/routes/safe/store/middleware/safeStorage' + +const watchedActions = [SET_CURRENT_CURRENCY, SET_CURRENCY_RATE, SET_CURRENCY_BALANCES] + +const currencyValuesStorageMiddleware = (store: Store) => (next: Function) => async ( + action: Action<*>, +) => { + const handledAction = next(action) + if (watchedActions.includes(action.type)) { + const state: GlobalState = store.getState() + const { dispatch } = store + switch (action.type) { + case SET_CURRENT_CURRENCY: { + const { safeAddress, selectedCurrency } = action.payload + dispatch(fetchCurrencyRate(safeAddress, selectedCurrency)) + break + } + case SET_CURRENCY_RATE: + case SET_CURRENCY_BALANCES: { + const currencyValues = currencyValuesSelector(state) + const currencyValuesWithoutBalances = currencyValues.map((currencyValue) => { + const currencyRate = currencyValue.get('currencyRate') + const selectedCurrency = currencyValue.get('selectedCurrency') + return { + currencyRate, + selectedCurrency, + } + }) + await saveCurrencyValues(currencyValuesWithoutBalances) + break + } + + default: + break + } + } + + return handledAction +} + +export default currencyValuesStorageMiddleware diff --git a/src/logic/currencyValues/store/model/currencyValues.js b/src/logic/currencyValues/store/model/currencyValues.js index f9d813a9..be509c94 100644 --- a/src/logic/currencyValues/store/model/currencyValues.js +++ b/src/logic/currencyValues/store/model/currencyValues.js @@ -1,5 +1,5 @@ // @flow -import type { RecordOf } from 'immutable' +import type { RecordFactory, RecordOf } from 'immutable' import { Record } from 'immutable' export const AVAILABLE_CURRENCIES = { @@ -45,17 +45,22 @@ export type BalanceCurrencyType = { balanceInSelectedCurrency: string, } -export const makeBalanceCurrency = Record({ +export const makeBalanceCurrency: RecordFactory = Record({ currencyName: '', tokenAddress: '', balanceInBaseCurrency: '', balanceInSelectedCurrency: '', }) -export type CurrencyValuesProps = { - currencyValueSelected: $Keys, - currencyRate: string, +export type CurrencyValuesEntry = { + selectedCurrency: $Keys, + currencyRate: number, currencyValuesList: BalanceCurrencyType[], } +export type CurrencyValuesProps = { + // Map safe address to currency values entry + currencyValues: Map, +} + export type CurrencyValues = RecordOf diff --git a/src/logic/currencyValues/store/reducer/currencyValues.js b/src/logic/currencyValues/store/reducer/currencyValues.js index 2d690fcd..17d75fd2 100644 --- a/src/logic/currencyValues/store/reducer/currencyValues.js +++ b/src/logic/currencyValues/store/reducer/currencyValues.js @@ -4,7 +4,7 @@ import { type ActionType, handleActions } from 'redux-actions' import { SET_CURRENCY_BALANCES } from '~/logic/currencyValues/store/actions/setCurrencyBalances' import { SET_CURRENCY_RATE } from '~/logic/currencyValues/store/actions/setCurrencyRate' -import { SET_CURRENT_CURRENCY } from '~/logic/currencyValues/store/actions/setCurrencySelected' +import { SET_CURRENT_CURRENCY } from '~/logic/currencyValues/store/actions/setSelectedCurrency' import type { State } from '~/logic/tokens/store/reducer/tokens' export const CURRENCY_VALUES_KEY = 'currencyValues' @@ -12,19 +12,19 @@ export const CURRENCY_VALUES_KEY = 'currencyValues' export default handleActions( { [SET_CURRENCY_RATE]: (state: State, action: ActionType): State => { - const { currencyRate } = action.payload + const { currencyRate, safeAddress } = action.payload - return state.set('currencyRate', currencyRate) + return state.setIn([safeAddress, 'currencyRate'], currencyRate) }, [SET_CURRENCY_BALANCES]: (state: State, action: ActionType): State => { - const { currencyBalances } = action.payload + const { currencyBalances, safeAddress } = action.payload - return state.set('currencyBalances', currencyBalances) + return state.setIn([safeAddress, 'currencyBalances'], currencyBalances) }, [SET_CURRENT_CURRENCY]: (state: State, action: ActionType): State => { - const { currencyValueSelected } = action.payload + const { safeAddress, selectedCurrency } = action.payload - return state.set('currencyValueSelected', currencyValueSelected) + return state.setIn([safeAddress, 'selectedCurrency'], selectedCurrency) }, }, Map(), diff --git a/src/logic/currencyValues/store/selectors/index.js b/src/logic/currencyValues/store/selectors/index.js index 429bfef5..0f13251b 100644 --- a/src/logic/currencyValues/store/selectors/index.js +++ b/src/logic/currencyValues/store/selectors/index.js @@ -1,13 +1,38 @@ // @flow import { List } from 'immutable' +import { type OutputSelector, createSelector } from 'reselect' +import type { CurrencyValuesEntry, CurrencyValuesProps } from '~/logic/currencyValues/store/model/currencyValues' import { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues' +import { safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors' import { type GlobalState } from '~/store' -export const currencyValuesListSelector = (state: GlobalState) => - state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([]) +export const currencyValuesSelector = (state: GlobalState): CurrencyValuesEntry => state[CURRENCY_VALUES_KEY] -export const currentCurrencySelector = (state: GlobalState) => state[CURRENCY_VALUES_KEY].get('currencyValueSelected') +export const safeFiatBalancesSelector: OutputSelector = createSelector( + currencyValuesSelector, + safeParamAddressFromStateSelector, + (currencyValues: CurrencyValuesProps, safeAddress: string) => { + if (!currencyValues) return + return currencyValues.get(safeAddress) + }, +) -export const currencyRateSelector = (state: GlobalState) => state[CURRENCY_VALUES_KEY].get('currencyRate') +export const safeFiatBalancesListSelector: OutputSelector = createSelector( + safeFiatBalancesSelector, + (currencyValuesMap: CurrencyValuesProps) => { + if (!currencyValuesMap) return + return currencyValuesMap.get('currencyBalances') ? currencyValuesMap.get('currencyBalances') : List([]) + }, +) + +export const currentCurrencySelector: OutputSelector = createSelector( + safeFiatBalancesSelector, + (currencyValuesMap?: CurrencyValuesProps) => (currencyValuesMap ? currencyValuesMap.get('selectedCurrency') : null), +) + +export const currencyRateSelector: OutputSelector = createSelector( + safeFiatBalancesSelector, + (currencyValuesMap: CurrencyValuesProps) => (currencyValuesMap ? currencyValuesMap.get('currencyRate') : null), +) diff --git a/src/logic/currencyValues/store/utils/currencyValuesStorage.js b/src/logic/currencyValues/store/utils/currencyValuesStorage.js new file mode 100644 index 00000000..29c69590 --- /dev/null +++ b/src/logic/currencyValues/store/utils/currencyValuesStorage.js @@ -0,0 +1,18 @@ +// @flow +import { Map } from 'immutable' + +import type { CurrencyValuesEntry } from '~/logic/currencyValues/store/model/currencyValues' +import { loadFromStorage, saveToStorage } from '~/utils/storage' + +const CURRENCY_VALUES_STORAGE_KEY = 'CURRENCY_VALUES_STORAGE_KEY' +export const saveCurrencyValues = async (currencyValues: Map) => { + try { + await saveToStorage(CURRENCY_VALUES_STORAGE_KEY, currencyValues) + } catch (err) { + console.error('Error storing currency values info in localstorage', err) + } +} + +export const loadCurrencyValues = async () => { + return (await loadFromStorage(CURRENCY_VALUES_STORAGE_KEY)) || {} +} diff --git a/src/logic/tokens/store/actions/fetchSafeTokens.js b/src/logic/tokens/store/actions/fetchSafeTokens.js index 276a9ae2..decd1cd4 100644 --- a/src/logic/tokens/store/actions/fetchSafeTokens.js +++ b/src/logic/tokens/store/actions/fetchSafeTokens.js @@ -35,7 +35,6 @@ const fetchSafeTokens = (safeAddress: string) => async (dispatch: ReduxDispatch< const alreadyActiveTokens = safe.get('activeTokens') const blacklistedTokens = safe.get('blacklistedTokens') const currencyValues = state[CURRENCY_VALUES_KEY] - const storedCurrencyBalances = currencyValues.get('currencyBalances') const { balances, currencyList, ethBalance, tokens } = result.data.reduce( (acc, { balance, balanceUsd, token, tokenAddress }) => { @@ -78,8 +77,14 @@ const fetchSafeTokens = (safeAddress: string) => async (dispatch: ReduxDispatch< const updateActiveTokens = activeTokens.equals(alreadyActiveTokens) ? noFunc : update({ activeTokens }) const updateBalances = balances.equals(safeBalances) ? noFunc : update({ balances }) const updateEthBalance = ethBalance === currentEthBalance ? noFunc : update({ ethBalance }) + const storedCurrencyBalances = + currencyValues && currencyValues.get(safeAddress) + ? currencyValues.get(safeAddress).get('currencyBalances') + : undefined - const updateCurrencies = currencyList.equals(storedCurrencyBalances) ? noFunc : setCurrencyBalances(currencyList) + const updateCurrencies = currencyList.equals(storedCurrencyBalances) + ? noFunc + : setCurrencyBalances(safeAddress, currencyList) const updateTokens = tokens.size === 0 ? noFunc : addTokens(tokens) diff --git a/src/routes/safe/components/Balances/Coins/index.jsx b/src/routes/safe/components/Balances/Coins/index.jsx index 315b34db..98bb886b 100644 --- a/src/routes/safe/components/Balances/Coins/index.jsx +++ b/src/routes/safe/components/Balances/Coins/index.jsx @@ -19,8 +19,8 @@ import Button from '~/components/layout/Button' import Row from '~/components/layout/Row' import { currencyRateSelector, - currencyValuesListSelector, currentCurrencySelector, + safeFiatBalancesListSelector, } from '~/logic/currencyValues/store/selectors' import { BALANCE_ROW_TEST_ID } from '~/routes/safe/components/Balances' import AssetTableCell from '~/routes/safe/components/Balances/AssetTableCell' @@ -46,16 +46,16 @@ const Coins = (props: Props) => { const classes = useStyles() const columns = generateColumns() const autoColumns = columns.filter((c) => !c.custom) - const currencySelected = useSelector(currentCurrencySelector) + const selectedCurrency = useSelector(currentCurrencySelector) const currencyRate = useSelector(currencyRateSelector) const activeTokens = useSelector(extendedSafeTokensSelector) - const currencyValues = useSelector(currencyValuesListSelector) + const currencyValues = useSelector(safeFiatBalancesListSelector) const granted = useSelector(grantedSelector) const [filteredData, setFilteredData] = React.useState(List()) React.useMemo(() => { - setFilteredData(getBalanceData(activeTokens, currencySelected, currencyValues, currencyRate)) - }, [currencySelected, currencyRate, activeTokens.hashCode(), currencyValues.hashCode()]) + setFilteredData(getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate)) + }, [selectedCurrency, currencyRate, activeTokens.hashCode(), currencyValues]) return ( diff --git a/src/routes/safe/components/DropdownCurrency/index.jsx b/src/routes/safe/components/DropdownCurrency/index.jsx index edb76daa..aaec6f3b 100644 --- a/src/routes/safe/components/DropdownCurrency/index.jsx +++ b/src/routes/safe/components/DropdownCurrency/index.jsx @@ -13,18 +13,19 @@ import { useDispatch, useSelector } from 'react-redux' import CheckIcon from './img/check.svg' -import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue' -import saveCurrencySelected from '~/logic/currencyValues/store/actions/saveCurrencySelected' +import { setSelectedCurrency } from '~/logic/currencyValues/store/actions/setSelectedCurrency' import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues' import { currentCurrencySelector } from '~/logic/currencyValues/store/selectors' import { useDropdownStyles } from '~/routes/safe/components/DropdownCurrency/style' +import { safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors' import { DropdownListTheme } from '~/theme/mui' const DropdownCurrency = () => { const currenciesList = Object.values(AVAILABLE_CURRENCIES) + const safeAddress = useSelector(safeParamAddressFromStateSelector) const dispatch = useDispatch() const [anchorEl, setAnchorEl] = useState(null) - const currencyValueSelected = useSelector(currentCurrencySelector) + const selectedCurrency = useSelector(currentCurrencySelector) const [searchParams, setSearchParams] = useState('') const classes = useDropdownStyles() @@ -41,17 +42,16 @@ const DropdownCurrency = () => { } const onCurrentCurrencyChangedHandler = (newCurrencySelectedName: AVAILABLE_CURRENCIES) => { - dispatch(fetchCurrencySelectedValue(newCurrencySelectedName)) - dispatch(saveCurrencySelected(newCurrencySelectedName)) + dispatch(setSelectedCurrency(safeAddress, newCurrencySelectedName)) handleClose() } - return !currencyValueSelected ? null : ( + return !selectedCurrency ? null : ( <> { /> - {currencyName === currencyValueSelected ? ( + {currencyName === selectedCurrency ? ( checked diff --git a/src/store/index.js b/src/store/index.js index 51ed1f28..4c75b999 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -15,6 +15,7 @@ import { nftTokensReducer, } from '~/logic/collectibles/store/reducer/collectibles' import cookies, { COOKIES_REDUCER_ID } from '~/logic/cookies/store/reducer/cookies' +import currencyValuesStorageMiddleware from '~/logic/currencyValues/store/middleware' import currencyValues, { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues' import currentSession, { CURRENT_SESSION_REDUCER_ID, @@ -55,6 +56,7 @@ const finalCreateStore = composeEnhancers( providerWatcher, notificationsMiddleware, addressBookMiddleware, + currencyValuesStorageMiddleware, ), )