* Creates a new notification: waitingConfirmation Adds key as optional parameter for notification Implemented getAwaitingTransactions to get the transactions that needs to be confirmed by the current user Not fetchTransactions action also dispatch a notification for awaiting transactions Improved performance of routes/safe/container/index to avoid re-rendering * Removes notification logic on fetchTransactions Adds notificationsMiddleware * Moves fetchTransaction to container * Removes unused param on fetchTransactions * Fixs null safe check * Fixs middleware declaration * Removes lodash * Changes cancelled transaction detection logic
This commit is contained in:
parent
56a6e16158
commit
f0b3172abe
|
@ -10,6 +10,7 @@ import { type Notification, NOTIFICATIONS } from './notificationTypes'
|
|||
export type NotificationsQueue = {
|
||||
beforeExecution: Notification | null,
|
||||
pendingExecution: Notification | null,
|
||||
waitingConfirmation: Notification | null,
|
||||
afterExecution: {
|
||||
noMoreConfirmationsNeeded: Notification | null,
|
||||
moreConfirmationsNeeded: Notification | null,
|
||||
|
@ -29,6 +30,15 @@ const standardTxNotificationsQueue: NotificationsQueue = {
|
|||
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
|
||||
}
|
||||
|
||||
const waitingTransactionNotificationsQueue: NotificationsQueue = {
|
||||
beforeExecution: null,
|
||||
pendingExecution: null,
|
||||
afterRejection: null,
|
||||
waitingConfirmation: NOTIFICATIONS.TX_WAITING_MSG,
|
||||
afterExecution: null,
|
||||
afterExecutionError: null,
|
||||
}
|
||||
|
||||
const confirmationTxNotificationsQueue: NotificationsQueue = {
|
||||
beforeExecution: NOTIFICATIONS.SIGN_TX_MSG,
|
||||
pendingExecution: NOTIFICATIONS.TX_CONFIRMATION_PENDING_MSG,
|
||||
|
@ -123,6 +133,10 @@ export const getNotificationsFromTxType = (txType: string) => {
|
|||
notificationsQueue = ownerNameChangeNotificationsQueue
|
||||
break
|
||||
}
|
||||
case TX_NOTIFICATION_TYPES.WAITING_TX: {
|
||||
notificationsQueue = waitingTransactionNotificationsQueue
|
||||
break
|
||||
}
|
||||
default: {
|
||||
notificationsQueue = defaultNotificationsQueue
|
||||
break
|
||||
|
@ -132,10 +146,12 @@ export const getNotificationsFromTxType = (txType: string) => {
|
|||
return notificationsQueue
|
||||
}
|
||||
|
||||
export const enhanceSnackbarForAction = (notification: Notification) => ({
|
||||
export const enhanceSnackbarForAction = (notification: Notification, key?: string, onClick?: Function) => ({
|
||||
...notification,
|
||||
key,
|
||||
options: {
|
||||
...notification.options,
|
||||
onClick,
|
||||
action: (key: number) => (
|
||||
<IconButton onClick={() => store.dispatch(closeSnackbarAction({ key }))}>
|
||||
<IconClose />
|
||||
|
|
|
@ -14,6 +14,7 @@ export type Variant = 'success' | 'error' | 'warning' | 'info'
|
|||
|
||||
export type Notification = {
|
||||
message: string,
|
||||
key?: string,
|
||||
options: {
|
||||
variant: Variant,
|
||||
persist: boolean,
|
||||
|
@ -38,6 +39,7 @@ export type Notifications = {
|
|||
TX_EXECUTED_MSG: Notification,
|
||||
TX_EXECUTED_MORE_CONFIRMATIONS_MSG: Notification,
|
||||
TX_FAILED_MSG: Notification,
|
||||
TX_WAITING_MSG: Notification,
|
||||
|
||||
// Approval Transactions
|
||||
TX_CONFIRMATION_PENDING_MSG: Notification,
|
||||
|
@ -122,6 +124,13 @@ export const NOTIFICATIONS: Notifications = {
|
|||
message: 'Transaction failed',
|
||||
options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
|
||||
},
|
||||
TX_WAITING_MSG: {
|
||||
message: 'A pending transaction requires your confirmation!',
|
||||
key: 'TX_WAITING_MSG',
|
||||
options: {
|
||||
variant: WARNING, persist: true, preventDuplicate: true,
|
||||
},
|
||||
},
|
||||
|
||||
// Approval Transactions
|
||||
TX_CONFIRMATION_PENDING_MSG: {
|
||||
|
|
|
@ -11,9 +11,8 @@ const addSnackbar = createAction<string, *>(ENQUEUE_SNACKBAR)
|
|||
const enqueueSnackbar = (notification: NotificationProps) => (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const newNotification = {
|
||||
...notification,
|
||||
key: new Date().getTime(),
|
||||
key: notification.key || new Date().getTime(),
|
||||
}
|
||||
|
||||
dispatch(addSnackbar(newNotification))
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import type { Transaction } from '~/routes/safe/store/models/transaction'
|
||||
|
||||
export const getAwaitingTransactions = (allTransactions: List<Transaction>, userAccount: string): List<Transaction> => {
|
||||
if (!allTransactions) {
|
||||
return List([])
|
||||
}
|
||||
|
||||
const allAwaitingTransactions = allTransactions.map((safeTransactions) => {
|
||||
const nonCancelledTransactions = safeTransactions.filter((transaction: Transaction) => {
|
||||
// If transactions are not executed, but there's a transaction with the same nonce EXECUTED later
|
||||
// it means that the transaction was cancelled (Replaced) and shouldn't get executed
|
||||
if (!transaction.isExecuted) {
|
||||
const replacementTransaction = safeTransactions.findLast(
|
||||
(tx) => tx.isExecuted && tx.nonce === transaction.nonce,
|
||||
)
|
||||
if (replacementTransaction) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
transaction = transaction.set('cancelled', true)
|
||||
}
|
||||
}
|
||||
// The transaction is not executed and is not cancelled, so it's still waiting confirmations
|
||||
if (!transaction.executionTxHash && !transaction.cancelled) {
|
||||
// Then we check if the waiting confirmations are not from the current user, otherwise, filters this transaction
|
||||
const transactionWaitingUser = transaction.confirmations.filter((confirmation) => confirmation.owner && confirmation.owner.address !== userAccount)
|
||||
|
||||
return transactionWaitingUser.size > 0
|
||||
}
|
||||
return false
|
||||
})
|
||||
return nonCancelledTransactions
|
||||
})
|
||||
|
||||
return allAwaitingTransactions
|
||||
}
|
|
@ -4,6 +4,7 @@ export type NotifiedTransaction = {
|
|||
STANDARD_TX: string,
|
||||
CONFIRMATION_TX: string,
|
||||
CANCELLATION_TX: string,
|
||||
WAITING_TX: string,
|
||||
SETTINGS_CHANGE_TX: string,
|
||||
SAFE_NAME_CHANGE_TX: string,
|
||||
OWNER_NAME_CHANGE_TX: string,
|
||||
|
@ -13,6 +14,7 @@ export const TX_NOTIFICATION_TYPES: NotifiedTransaction = {
|
|||
STANDARD_TX: 'STANDARD_TX',
|
||||
CONFIRMATION_TX: 'CONFIRMATION_TX',
|
||||
CANCELLATION_TX: 'CANCELLATION_TX',
|
||||
WAITING_TX: 'WAITING_TX',
|
||||
SETTINGS_CHANGE_TX: 'SETTINGS_CHANGE_TX',
|
||||
SAFE_NAME_CHANGE_TX: 'SAFE_NAME_CHANGE_TX',
|
||||
OWNER_NAME_CHANGE_TX: 'OWNER_NAME_CHANGE_TX',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import React, { useEffect } from 'react'
|
||||
import React from 'react'
|
||||
import { List } from 'immutable'
|
||||
import TxsTable from '~/routes/safe/components/Transactions/TxsTable'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
|
@ -8,7 +8,6 @@ import { type Owner } from '~/routes/safe/store/models/owner'
|
|||
type Props = {
|
||||
safeAddress: string,
|
||||
threshold: number,
|
||||
fetchTransactions: Function,
|
||||
transactions: List<Transaction>,
|
||||
owners: List<Owner>,
|
||||
userAddress: string,
|
||||
|
@ -18,8 +17,6 @@ type Props = {
|
|||
currentNetwork: string,
|
||||
}
|
||||
|
||||
const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 5000
|
||||
|
||||
const Transactions = ({
|
||||
transactions = List(),
|
||||
owners,
|
||||
|
@ -29,22 +26,8 @@ const Transactions = ({
|
|||
safeAddress,
|
||||
createTransaction,
|
||||
processTransaction,
|
||||
fetchTransactions,
|
||||
currentNetwork,
|
||||
}: Props) => {
|
||||
let intervalId: IntervalID
|
||||
|
||||
useEffect(() => {
|
||||
fetchTransactions(safeAddress)
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
fetchTransactions(safeAddress)
|
||||
}, TIMEOUT)
|
||||
|
||||
return () => clearInterval(intervalId)
|
||||
}, [safeAddress])
|
||||
|
||||
return (
|
||||
}: Props) => (
|
||||
<TxsTable
|
||||
transactions={transactions}
|
||||
threshold={threshold}
|
||||
|
@ -57,6 +40,5 @@ const Transactions = ({
|
|||
processTransaction={processTransaction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Transactions
|
||||
|
|
|
@ -34,11 +34,14 @@ class SafeView extends React.Component<Props, State> {
|
|||
|
||||
componentDidMount() {
|
||||
const {
|
||||
fetchSafe, activeTokens, safeUrl, fetchTokenBalances, fetchTokens,
|
||||
fetchSafe, activeTokens, safeUrl, fetchTokenBalances, fetchTokens, fetchTransactions, safe,
|
||||
} = this.props
|
||||
|
||||
fetchSafe(safeUrl)
|
||||
fetchTokenBalances(safeUrl, activeTokens)
|
||||
if (safe && safe.address) {
|
||||
fetchTransactions(safe.address)
|
||||
}
|
||||
|
||||
// fetch tokens there to get symbols for tokens in TXs list
|
||||
fetchTokens()
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
import type { AnyAction, Store } from 'redux'
|
||||
import { push } from 'connected-react-router'
|
||||
import { type GlobalState } from '~/store/'
|
||||
import { ADD_TRANSACTIONS } from '~/routes/safe/store/actions/addTransactions'
|
||||
import { getAwaitingTransactions } from '~/logic/safe/transactions/awaitingTransactions'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import { enhanceSnackbarForAction, NOTIFICATIONS } from '~/logic/notifications'
|
||||
import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar'
|
||||
|
||||
const watchedActions = [
|
||||
ADD_TRANSACTIONS,
|
||||
]
|
||||
|
||||
const notificationsMiddleware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
||||
const handledAction = next(action)
|
||||
const { dispatch } = store
|
||||
|
||||
if (watchedActions.includes(action.type)) {
|
||||
const state: GlobalState = store.getState()
|
||||
switch (action.type) {
|
||||
case ADD_TRANSACTIONS: {
|
||||
const transactionsList = action.payload
|
||||
const userAddress: string = userAccountSelector(state)
|
||||
const awaitingTransactions = getAwaitingTransactions(transactionsList, userAddress)
|
||||
|
||||
|
||||
awaitingTransactions.map((awaitingTransactionsList, safeAddress) => {
|
||||
const convertedList = awaitingTransactionsList.toJS()
|
||||
const notificationKey = `${safeAddress}-${userAddress}`
|
||||
const onNotificationClicked = () => {
|
||||
dispatch(closeSnackbarAction({ key: notificationKey }))
|
||||
dispatch(push(`/safes/${safeAddress}/transactions`))
|
||||
}
|
||||
if (convertedList.length > 0) {
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.TX_WAITING_MSG, notificationKey, onNotificationClicked)))
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return handledAction
|
||||
}
|
||||
|
||||
export default notificationsMiddleware
|
|
@ -19,6 +19,7 @@ import notifications, {
|
|||
type NotificationReducerState as NotificationsState,
|
||||
} from '~/logic/notifications/store/reducer/notifications'
|
||||
import cookies, { COOKIES_REDUCER_ID } from '~/logic/cookies/store/reducer/cookies'
|
||||
import notificationsMiddleware from '~/routes/safe/store/middleware/notificationsMiddleware'
|
||||
|
||||
|
||||
export const history = createBrowserHistory()
|
||||
|
@ -26,7 +27,7 @@ export const history = createBrowserHistory()
|
|||
// eslint-disable-next-line
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
const finalCreateStore = composeEnhancers(
|
||||
applyMiddleware(thunk, routerMiddleware(history), safeStorage, providerWatcher),
|
||||
applyMiddleware(thunk, routerMiddleware(history), safeStorage, providerWatcher, notificationsMiddleware),
|
||||
)
|
||||
|
||||
export type GlobalState = {
|
||||
|
|
Loading…
Reference in New Issue