From 7637b322bb2d41ed6e3dbff09cf1c1154ceddd44 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Thu, 21 Sep 2023 22:25:41 +0200 Subject: [PATCH] Feat: actionable pending txs on the Dashboard (#2523) * Feat: actionable pending txs on the Dashboard * Fix e2e test * Rename vars * Txs -> transactions --- cypress/e2e/pages/dashboard.pages.js | 2 +- .../PendingTxs/PendingTxListItem.tsx | 20 +++- .../dashboard/PendingTxs/PendingTxsList.tsx | 104 ++++++++---------- .../dashboard/PendingTxs/styles.module.css | 17 +++ src/components/dashboard/index.tsx | 2 +- .../transactions/BatchExecuteButton/index.tsx | 2 +- .../transactions/ExecuteTxButton/index.tsx | 1 + .../transactions/SignTxButton/index.tsx | 1 + 8 files changed, 86 insertions(+), 63 deletions(-) diff --git a/cypress/e2e/pages/dashboard.pages.js b/cypress/e2e/pages/dashboard.pages.js index e8a10fffb3..d862ab987c 100644 --- a/cypress/e2e/pages/dashboard.pages.js +++ b/cypress/e2e/pages/dashboard.pages.js @@ -1,7 +1,7 @@ import * as constants from '../../support/constants' const connectAndTransactStr = 'Connect & transact' -const transactionQueueStr = 'Transaction queue' +const transactionQueueStr = 'Pending transactions' const noTransactionStr = 'This Safe has no queued transactions' const overviewStr = 'Overview' const viewAssetsStr = 'View assets' diff --git a/src/components/dashboard/PendingTxs/PendingTxListItem.tsx b/src/components/dashboard/PendingTxs/PendingTxListItem.tsx index 2ae589338d..1ac2855c73 100644 --- a/src/components/dashboard/PendingTxs/PendingTxListItem.tsx +++ b/src/components/dashboard/PendingTxs/PendingTxListItem.tsx @@ -2,16 +2,20 @@ import NextLink from 'next/link' import { useRouter } from 'next/router' import type { ReactElement } from 'react' import { useMemo } from 'react' +import { TransactionInfoType } from '@safe-global/safe-gateway-typescript-sdk' import ChevronRight from '@mui/icons-material/ChevronRight' import type { TransactionSummary } from '@safe-global/safe-gateway-typescript-sdk' import { Box, SvgIcon, Typography } from '@mui/material' -import { isMultisigExecutionInfo } from '@/utils/transaction-guards' +import { isExecutable, isMultisigExecutionInfo, isSignableBy } from '@/utils/transaction-guards' import TxInfo from '@/components/transactions/TxInfo' import TxType from '@/components/transactions/TxType' import css from './styles.module.css' import OwnersIcon from '@/public/images/common/owners.svg' import { AppRoutes } from '@/config/routes' -import { TransactionInfoType } from '@safe-global/safe-gateway-typescript-sdk' +import useSafeInfo from '@/hooks/useSafeInfo' +import useWallet from '@/hooks/wallets/useWallet' +import SignTxButton from '@/components/transactions/SignTxButton' +import ExecuteTxButton from '@/components/transactions/ExecuteTxButton' type PendingTxType = { transaction: TransactionSummary @@ -20,6 +24,10 @@ type PendingTxType = { const PendingTx = ({ transaction }: PendingTxType): ReactElement => { const router = useRouter() const { id } = transaction + const { safe } = useSafeInfo() + const wallet = useWallet() + const canSign = wallet ? isSignableBy(transaction, wallet.address) : false + const canExecute = wallet ? isExecutable(transaction, wallet?.address, safe) : false const url = useMemo( () => ({ @@ -60,7 +68,13 @@ const PendingTx = ({ transaction }: PendingTxType): ReactElement => { )} - + {canExecute ? ( + + ) : canSign ? ( + + ) : ( + + )} ) diff --git a/src/components/dashboard/PendingTxs/PendingTxsList.tsx b/src/components/dashboard/PendingTxs/PendingTxsList.tsx index 28cf208d0f..263b5a726b 100644 --- a/src/components/dashboard/PendingTxs/PendingTxsList.tsx +++ b/src/components/dashboard/PendingTxs/PendingTxsList.tsx @@ -2,31 +2,18 @@ import type { ReactElement } from 'react' import { useMemo } from 'react' import { useRouter } from 'next/router' import { getLatestTransactions } from '@/utils/tx-list' -import styled from '@emotion/styled' import { Box, Skeleton, Typography } from '@mui/material' import { Card, ViewAllLink, WidgetBody, WidgetContainer } from '../styled' import PendingTxListItem from './PendingTxListItem' import useTxQueue from '@/hooks/useTxQueue' import { AppRoutes } from '@/config/routes' import NoTransactionsIcon from '@/public/images/transactions/no-transactions.svg' -import { getQueuedTransactionCount } from '@/utils/transactions' +import css from './styles.module.css' +import { isSignableBy, isExecutable } from '@/utils/transaction-guards' +import useWallet from '@/hooks/wallets/useWallet' +import useSafeInfo from '@/hooks/useSafeInfo' -const SkeletonWrapper = styled.div` - border-radius: 8px; - overflow: hidden; -` - -const StyledList = styled.div` - display: flex; - flex-direction: column; - gap: var(--space-1); - width: 100%; -` - -const StyledWidgetTitle = styled.div` - display: flex; - justify-content: space-between; -` +const MAX_TXS = 4 const EmptyState = () => { return ( @@ -42,60 +29,63 @@ const EmptyState = () => { ) } -const PendingTxsList = ({ size = 4 }: { size?: number }): ReactElement | null => { +const LoadingState = () => ( +
+ {Array.from(Array(MAX_TXS).keys()).map((key) => ( + + ))} +
+) + +const PendingTxsList = (): ReactElement | null => { + const router = useRouter() const { page, loading } = useTxQueue() + const { safe } = useSafeInfo() + const wallet = useWallet() const queuedTxns = useMemo(() => getLatestTransactions(page?.results), [page?.results]) - const queuedTxsToDisplay = queuedTxns.slice(0, size) - const totalQueuedTxs = getQueuedTransactionCount(page) - const router = useRouter() + + const actionableTxs = useMemo(() => { + return wallet + ? queuedTxns.filter( + (tx) => isSignableBy(tx.transaction, wallet.address) || isExecutable(tx.transaction, wallet.address, safe), + ) + : queuedTxns + }, [wallet, queuedTxns, safe]) + + const txs = actionableTxs.length ? actionableTxs : queuedTxns + const txsToDisplay = txs.slice(0, MAX_TXS) const queueUrl = useMemo( () => ({ pathname: AppRoutes.transactions.queue, query: { safe: router.query.safe }, }), - [router], + [router.query.safe], ) - const LoadingState = useMemo( - () => ( - - {Array.from(Array(size).keys()).map((key) => ( - - - - ))} - - ), - [size], - ) - - const ResultState = useMemo( - () => ( - - {queuedTxsToDisplay.map((transaction) => ( - - ))} - - ), - [queuedTxsToDisplay], - ) - - const getWidgetBody = () => { - if (loading) return LoadingState - if (!queuedTxsToDisplay.length) return - return ResultState - } - return ( - +
- Transaction queue {totalQueuedTxs ? ` (${totalQueuedTxs})` : ''} + Pending transactions + {queuedTxns.length > 0 && } - - {getWidgetBody()} +
+ + + {loading ? ( + + ) : queuedTxns.length ? ( +
+ {txsToDisplay.map((tx) => ( + + ))} +
+ ) : ( + + )} +
) } diff --git a/src/components/dashboard/PendingTxs/styles.module.css b/src/components/dashboard/PendingTxs/styles.module.css index e62e170746..ad2a6fe17e 100644 --- a/src/components/dashboard/PendingTxs/styles.module.css +++ b/src/components/dashboard/PendingTxs/styles.module.css @@ -15,6 +15,23 @@ border-color: var(--color-secondary-light); } +.list { + display: flex; + flex-direction: column; + gap: var(--space-1); + width: 100%; +} + +.skeleton { + border-radius: 8px; + overflow: hidden; +} + +.title { + display: flex; + justify-content: space-between; +} + .confirmationsCount { display: flex; align-items: center; diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index 713abbe444..497aed2b7e 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -25,7 +25,7 @@ const Dashboard = (): ReactElement => { - + diff --git a/src/components/transactions/BatchExecuteButton/index.tsx b/src/components/transactions/BatchExecuteButton/index.tsx index a9daefb3a8..98365d7525 100644 --- a/src/components/transactions/BatchExecuteButton/index.tsx +++ b/src/components/transactions/BatchExecuteButton/index.tsx @@ -60,7 +60,7 @@ const BatchExecuteButton = () => { disabled={isDisabled} onClick={handleOpenModal} > - Bulk execute{isBatchable && ` ${batchableTransactions.length} txs`} + Bulk execute{isBatchable && ` ${batchableTransactions.length} transactions`} diff --git a/src/components/transactions/ExecuteTxButton/index.tsx b/src/components/transactions/ExecuteTxButton/index.tsx index 1cc4d8e60a..1791c43394 100644 --- a/src/components/transactions/ExecuteTxButton/index.tsx +++ b/src/components/transactions/ExecuteTxButton/index.tsx @@ -38,6 +38,7 @@ const ExecuteTxButton = ({ const onClick = (e: SyntheticEvent) => { e.stopPropagation() + e.preventDefault() setTxFlow(, undefined, false) } diff --git a/src/components/transactions/SignTxButton/index.tsx b/src/components/transactions/SignTxButton/index.tsx index fbd35d7901..f05705ddf4 100644 --- a/src/components/transactions/SignTxButton/index.tsx +++ b/src/components/transactions/SignTxButton/index.tsx @@ -35,6 +35,7 @@ const SignTxButton = ({ const onClick = (e: SyntheticEvent) => { e.stopPropagation() + e.preventDefault() setTxFlow(, undefined, false) }