diff --git a/package-lock.json b/package-lock.json index 3ac328ca..19a06b26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "safe-react", - "version": "1.6.1", + "version": "1.6.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index de70d017..91c4ad61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "safe-react", - "version": "1.6.1", + "version": "1.6.2", "description": "Allowing crypto users manage funds in a safer way", "homepage": "https://github.com/gnosis/safe-react#readme", "bugs": { diff --git a/src/index.js b/src/index.js index 4871d15d..f7d264ac 100644 --- a/src/index.js +++ b/src/index.js @@ -7,8 +7,8 @@ import Root from '~/components/Root' import loadActiveTokens from '~/logic/tokens/store/actions/loadActiveTokens' import loadDefaultSafe from '~/routes/safe/store/actions/loadDefaultSafe' import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage' +import loadCurrentSessionFromStorage from '~/logic/currentSession/store/actions/loadCurrentSessionFromStorage' import { store } from '~/store' -import verifyRecurringUser from '~/utils/verifyRecurringUser' BigNumber.set({ EXPONENTIAL_AT: [-7, 255] }) @@ -20,9 +20,12 @@ if (process.env.NODE_ENV !== 'production') { // $FlowFixMe store.dispatch(loadActiveTokens()) +// $FlowFixMe store.dispatch(loadSafesFromStorage()) +// $FlowFixMe store.dispatch(loadDefaultSafe()) -verifyRecurringUser() +// $FlowFixMe +store.dispatch(loadCurrentSessionFromStorage()) const root = document.getElementById('root') diff --git a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.js b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.js index a262158c..0a5624ae 100644 --- a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.js +++ b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.js @@ -7,7 +7,7 @@ const fetchTokenCurrenciesBalances = (safeAddress: string) => { return null } const apiUrl = getTxServiceHost() - const url = `${apiUrl}safes/${safeAddress}/balances/usd` + const url = `${apiUrl}safes/${safeAddress}/balances/usd/` return axios.get(url) } diff --git a/src/logic/currentSession/store/actions/addViewedSafe.js b/src/logic/currentSession/store/actions/addViewedSafe.js new file mode 100644 index 00000000..d28529a4 --- /dev/null +++ b/src/logic/currentSession/store/actions/addViewedSafe.js @@ -0,0 +1,10 @@ +// @flow +import { type Dispatch as ReduxDispatch } from 'redux' +import { type GlobalState } from '~/store' +import updateViewedSafes from '~/logic/currentSession/store/actions/updateViewedSafes' + +const addViewedSafe = (safeAddress: string) => (dispatch: ReduxDispatch) => { + dispatch(updateViewedSafes(safeAddress)) +} + +export default addViewedSafe diff --git a/src/logic/currentSession/store/actions/loadCurrentSession.js b/src/logic/currentSession/store/actions/loadCurrentSession.js new file mode 100644 index 00000000..78dfbbb5 --- /dev/null +++ b/src/logic/currentSession/store/actions/loadCurrentSession.js @@ -0,0 +1,8 @@ +// @flow +import { createAction } from 'redux-actions' + +export const LOAD_CURRENT_SESSION = 'LOAD_CURRENT_SESSION' + +const loadCurrentSession = createAction(LOAD_CURRENT_SESSION) + +export default loadCurrentSession diff --git a/src/logic/currentSession/store/actions/loadCurrentSessionFromStorage.js b/src/logic/currentSession/store/actions/loadCurrentSessionFromStorage.js new file mode 100644 index 00000000..4ea6e474 --- /dev/null +++ b/src/logic/currentSession/store/actions/loadCurrentSessionFromStorage.js @@ -0,0 +1,14 @@ +// @flow +import { type Dispatch as ReduxDispatch } from 'redux' +import { type GlobalState } from '~/store' +import { getCurrentSessionFromStorage } from '~/logic/currentSession/utils' +import loadCurrentSession from '~/logic/currentSession/store/actions/loadCurrentSession' +import { makeCurrentSession } from '~/logic/currentSession/store/model/currentSession' + +const loadCurrentSessionFromStorage = () => async (dispatch: ReduxDispatch) => { + const currentSession = (await getCurrentSessionFromStorage()) + + dispatch(loadCurrentSession(makeCurrentSession(currentSession ? currentSession : {}))) +} + +export default loadCurrentSessionFromStorage diff --git a/src/logic/currentSession/store/actions/updateViewedSafes.js b/src/logic/currentSession/store/actions/updateViewedSafes.js new file mode 100644 index 00000000..2c4aeebf --- /dev/null +++ b/src/logic/currentSession/store/actions/updateViewedSafes.js @@ -0,0 +1,8 @@ +// @flow +import { createAction } from 'redux-actions' + +export const UPDATE_VIEWED_SAFES = 'UPDATE_VIEWED_SAFES' + +const updateViewedSafes = createAction(UPDATE_VIEWED_SAFES) + +export default updateViewedSafes diff --git a/src/logic/currentSession/store/model/currentSession.js b/src/logic/currentSession/store/model/currentSession.js new file mode 100644 index 00000000..51d82937 --- /dev/null +++ b/src/logic/currentSession/store/model/currentSession.js @@ -0,0 +1,14 @@ +// @flow +import { + Record, type RecordFactory, type RecordOf, +} from 'immutable' + +export type CurrentSessionProps = { + viewedSafes: Array, +} + +export const makeCurrentSession: RecordFactory = Record({ + viewedSafes: [], +}) + +export type CurrentSession = RecordOf diff --git a/src/logic/currentSession/store/reducer/currentSession.js b/src/logic/currentSession/store/reducer/currentSession.js new file mode 100644 index 00000000..ccc261df --- /dev/null +++ b/src/logic/currentSession/store/reducer/currentSession.js @@ -0,0 +1,32 @@ +// @flow +import { Map } from 'immutable' +import { handleActions, type ActionType } from 'redux-actions' +import { LOAD_CURRENT_SESSION } from '~/logic/currentSession/store/actions/loadCurrentSession' +import { UPDATE_VIEWED_SAFES } from '~/logic/currentSession/store/actions/updateViewedSafes' +import { saveCurrentSessionToStorage } from '~/logic/currentSession/utils' + +export const CURRENT_SESSION_REDUCER_ID = 'currentSession' + +export type State = Map + +export default handleActions( + { + [LOAD_CURRENT_SESSION]: ( + state: State, + action: ActionType, + ): State => state.merge(Map(action.payload)), + [UPDATE_VIEWED_SAFES]: (state: State, action: ActionType): State => { + const safeAddress = action.payload + + const newState = state.updateIn( + ['viewedSafes'], + (prev) => (prev.includes(safeAddress) ? prev : [...prev, safeAddress]), + ) + + saveCurrentSessionToStorage(newState) + + return newState + }, + }, + Map(), +) diff --git a/src/logic/currentSession/utils/index.js b/src/logic/currentSession/utils/index.js new file mode 100644 index 00000000..d0848a9b --- /dev/null +++ b/src/logic/currentSession/utils/index.js @@ -0,0 +1,15 @@ +// @flow +import type { CurrentSession, CurrentSessionProps } from '~/logic/currentSession/store/model/currentSession' +import { loadFromStorage, saveToStorage } from '~/utils/storage' + +const CURRENT_SESSION_STORAGE_KEY = 'CURRENT_SESSION' + +export const getCurrentSessionFromStorage = async (): Promise => loadFromStorage(CURRENT_SESSION_STORAGE_KEY) + +export const saveCurrentSessionToStorage = async (currentSession: CurrentSession): Promise<*> => { + try { + await saveToStorage(CURRENT_SESSION_STORAGE_KEY, currentSession.toJSON()) + } catch (err) { + console.error('Error storing currentSession in localStorage', err) + } +} diff --git a/src/routes/load/container/Load.jsx b/src/routes/load/container/Load.jsx index 6303b337..c02f1da0 100644 --- a/src/routes/load/container/Load.jsx +++ b/src/routes/load/container/Load.jsx @@ -47,7 +47,7 @@ class Load extends React.Component { await loadSafe(safeName, safeAddress, owners, addSafe) - const url = `${SAFELIST_ADDRESS}/${safeAddress}/balances` + const url = `${SAFELIST_ADDRESS}/${safeAddress}/balances/` history.push(url) } catch (error) { console.error('Error while loading the Safe', error) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx index dc317ccb..502d90da 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx @@ -70,6 +70,7 @@ const ApproveTxModal = ({ const [gasCosts, setGasCosts] = useState('< 0.001') const { title, description } = getModalTitleAndDescription(thresholdReached) const oneConfirmationLeft = !thresholdReached && tx.confirmations.size + 1 === threshold + const isTheTxReadyToBeExecuted = oneConfirmationLeft ? true : thresholdReached useEffect(() => { let isCurrent = true @@ -109,7 +110,7 @@ const ApproveTxModal = ({ notifiedTransaction: TX_NOTIFICATION_TYPES.CONFIRMATION_TX, enqueueSnackbar, closeSnackbar, - approveAndExecute: canExecute && approveAndExecute && oneConfirmationLeft, + approveAndExecute: canExecute && approveAndExecute && isTheTxReadyToBeExecuted, }) onClose() } diff --git a/src/routes/safe/container/actions.js b/src/routes/safe/container/actions.js index 459715f3..1bc69783 100644 --- a/src/routes/safe/container/actions.js +++ b/src/routes/safe/container/actions.js @@ -9,6 +9,7 @@ import updateSafe from '~/routes/safe/store/actions/updateSafe' import fetchTokens from '~/logic/tokens/store/actions/fetchTokens' import fetchCurrencyValues from '~/logic/currencyValues/store/actions/fetchCurrencyValues' import activateTokensByBalance from '~/logic/tokens/store/actions/activateTokensByBalance' +import addViewedSafe from '~/logic/currentSession/store/actions/addViewedSafe' export type Actions = { fetchSafe: typeof fetchSafe, @@ -21,7 +22,8 @@ export type Actions = { fetchEtherBalance: typeof fetchEtherBalance, activateTokensByBalance: typeof activateTokensByBalance, checkAndUpdateSafeOwners: typeof checkAndUpdateSafe, - fetchCurrencyValues: typeof fetchCurrencyValues + fetchCurrencyValues: typeof fetchCurrencyValues, + addViewedSafe: typeof addViewedSafe, } export default { @@ -36,4 +38,5 @@ export default { fetchEtherBalance, fetchCurrencyValues, checkAndUpdateSafeOwners: checkAndUpdateSafe, + addViewedSafe, } diff --git a/src/routes/safe/container/index.jsx b/src/routes/safe/container/index.jsx index 71c08ae2..e0e9c7d8 100644 --- a/src/routes/safe/container/index.jsx +++ b/src/routes/safe/container/index.jsx @@ -34,13 +34,22 @@ class SafeView extends React.Component { componentDidMount() { const { - fetchSafe, activeTokens, safeUrl, fetchTokenBalances, fetchTokens, fetchTransactions, fetchCurrencyValues, + fetchSafe, + activeTokens, + safeUrl, + fetchTokenBalances, + fetchTokens, + fetchTransactions, + fetchCurrencyValues, + addViewedSafe, } = this.props - fetchSafe(safeUrl).then(() => { - // The safe needs to be loaded before fetching the transactions - fetchTransactions(safeUrl) - }) + fetchSafe(safeUrl) + .then(() => { + // The safe needs to be loaded before fetching the transactions + fetchTransactions(safeUrl) + addViewedSafe(safeUrl) + }) fetchTokenBalances(safeUrl, activeTokens) // fetch tokens there to get symbols for tokens in TXs list fetchTokens() diff --git a/src/routes/safe/store/middleware/notificationsMiddleware.js b/src/routes/safe/store/middleware/notificationsMiddleware.js index 4ccc0398..08555d56 100644 --- a/src/routes/safe/store/middleware/notificationsMiddleware.js +++ b/src/routes/safe/store/middleware/notificationsMiddleware.js @@ -12,9 +12,6 @@ import { enhanceSnackbarForAction, NOTIFICATIONS } from '~/logic/notifications' import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar' import { getIncomingTxAmount } from '~/routes/safe/components/Transactions/TxsTable/columns' import updateSafe from '~/routes/safe/store/actions/updateSafe' -import { loadFromStorage } from '~/utils/storage' -import { SAFES_KEY } from '~/logic/safe/utils' -import { RECURRING_USER_KEY } from '~/utils/verifyRecurringUser' import { safesMapSelector } from '~/routes/safe/store/selectors' import { isUserOwner } from '~/logic/wallets/ethAddresses' @@ -69,17 +66,16 @@ const notificationsMiddleware = (store: Store) => ( break } case ADD_INCOMING_TRANSACTIONS: { - action.payload.forEach(async (incomingTransactions, safeAddress) => { - const storedSafes = await loadFromStorage(SAFES_KEY) - const latestIncomingTxBlock = storedSafes - ? storedSafes[safeAddress].latestIncomingTxBlock - : 0 + action.payload.forEach((incomingTransactions, safeAddress) => { + const { latestIncomingTxBlock } = state.safes.get('safes').get(safeAddress) + const viewedSafes = state.currentSession ? state.currentSession.get('viewedSafes') : [] + const recurringUser = viewedSafes.includes(safeAddress) const newIncomingTransactions = incomingTransactions.filter( (tx) => tx.blockNumber > latestIncomingTxBlock, ) + const { message, ...TX_INCOMING_MSG } = NOTIFICATIONS.TX_INCOMING_MSG - const recurringUser = await loadFromStorage(RECURRING_USER_KEY) if (recurringUser) { if (newIncomingTransactions.size > 3) { @@ -109,7 +105,7 @@ const notificationsMiddleware = (store: Store) => ( updateSafe({ address: safeAddress, latestIncomingTxBlock: newIncomingTransactions.size - ? newIncomingTransactions.last().blockNumber + ? newIncomingTransactions.first().blockNumber : latestIncomingTxBlock, }), ) diff --git a/src/routes/safe/store/models/safe.js b/src/routes/safe/store/models/safe.js index 63bebb1b..2f83e673 100644 --- a/src/routes/safe/store/models/safe.js +++ b/src/routes/safe/store/models/safe.js @@ -16,6 +16,7 @@ export type SafeProps = { ethBalance?: string, nonce: number, latestIncomingTxBlock: number, + recurringUser?: boolean, } const SafeRecord: RecordFactory = Record({ @@ -29,6 +30,7 @@ const SafeRecord: RecordFactory = Record({ balances: Map({}), nonce: 0, latestIncomingTxBlock: 0, + recurringUser: undefined, }) export type Safe = RecordOf diff --git a/src/store/index.js b/src/store/index.js index 042eff44..e3e6caf3 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -25,7 +25,10 @@ import notifications, { import currencyValues, { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues' import cookies, { COOKIES_REDUCER_ID } from '~/logic/cookies/store/reducer/cookies' import notificationsMiddleware from '~/routes/safe/store/middleware/notificationsMiddleware' - +import currentSession, { + CURRENT_SESSION_REDUCER_ID, + type State as CurrentSessionState, +} from '~/logic/currentSession/store/reducer/currentSession' export const history = createBrowserHistory() @@ -42,6 +45,7 @@ export type GlobalState = { transactions: TransactionsState, incomingTransactions: IncomingTransactionsState, notifications: NotificationsState, + currentSession: CurrentSessionState, } export type GetState = () => GlobalState @@ -56,6 +60,7 @@ const reducers: Reducer = combineReducers({ [NOTIFICATIONS_REDUCER_ID]: notifications, [CURRENCY_VALUES_KEY]: currencyValues, [COOKIES_REDUCER_ID]: cookies, + [CURRENT_SESSION_REDUCER_ID]: currentSession, }) export const store: Store = createStore(reducers, finalCreateStore) diff --git a/src/test/builder/safe.dom.utils.js b/src/test/builder/safe.dom.utils.js index 729864ed..bb2a8aab 100644 --- a/src/test/builder/safe.dom.utils.js +++ b/src/test/builder/safe.dom.utils.js @@ -102,7 +102,7 @@ const renderApp = (store: Store) => ({ export const renderSafeView = (store: Store, address: string) => { const app = renderApp(store) - const url = `${SAFELIST_ADDRESS}/${address}/balances` + const url = `${SAFELIST_ADDRESS}/${address}/balances/` history.push(url) return app diff --git a/src/theme/mui.js b/src/theme/mui.js index 4b1a516a..0babc495 100644 --- a/src/theme/mui.js +++ b/src/theme/mui.js @@ -73,6 +73,9 @@ const theme = createMuiTheme({ }, containedSecondary: { backgroundColor: error, + '&:hover': { + backgroundColor: '#d4d5d3', + }, }, outlinedPrimary: { border: `2px solid ${primary}`, diff --git a/src/utils/verifyRecurringUser.js b/src/utils/verifyRecurringUser.js deleted file mode 100644 index 6c0456c9..00000000 --- a/src/utils/verifyRecurringUser.js +++ /dev/null @@ -1,18 +0,0 @@ -// @flow -import { loadFromStorage, saveToStorage } from '~/utils/storage' - -export const RECURRING_USER_KEY = 'RECURRING_USER' - -const verifyRecurringUser = async () => { - const recurringUser = await loadFromStorage(RECURRING_USER_KEY) - - if (recurringUser === undefined) { - await saveToStorage(RECURRING_USER_KEY, false) - } - - if (recurringUser === false) { - await saveToStorage(RECURRING_USER_KEY, true) - } -} - -export default verifyRecurringUser