Skip to content

Commit

Permalink
Feat: actionable pending txs on the Dashboard (#2523)
Browse files Browse the repository at this point in the history
* Feat: actionable pending txs on the Dashboard

* Fix e2e test

* Rename vars

* Txs -> transactions
  • Loading branch information
katspaugh authored Sep 21, 2023
1 parent 37e6faf commit 7637b32
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 63 deletions.
2 changes: 1 addition & 1 deletion cypress/e2e/pages/dashboard.pages.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
20 changes: 17 additions & 3 deletions src/components/dashboard/PendingTxs/PendingTxListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
() => ({
Expand Down Expand Up @@ -60,7 +68,13 @@ const PendingTx = ({ transaction }: PendingTxType): ReactElement => {
<Box flexGrow={1} />
)}

<ChevronRight color="border" />
{canExecute ? (
<ExecuteTxButton txSummary={transaction} compact />
) : canSign ? (
<SignTxButton txSummary={transaction} compact />
) : (
<ChevronRight color="border" />
)}
</Box>
</NextLink>
)
Expand Down
104 changes: 47 additions & 57 deletions src/components/dashboard/PendingTxs/PendingTxsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -42,60 +29,63 @@ const EmptyState = () => {
)
}

const PendingTxsList = ({ size = 4 }: { size?: number }): ReactElement | null => {
const LoadingState = () => (
<div className={css.list}>
{Array.from(Array(MAX_TXS).keys()).map((key) => (
<Skeleton key={key} variant="rectangular" height={52} />
))}
</div>
)

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(
() => (
<StyledList>
{Array.from(Array(size).keys()).map((key) => (
<SkeletonWrapper key={key}>
<Skeleton variant="rectangular" height={52} />
</SkeletonWrapper>
))}
</StyledList>
),
[size],
)

const ResultState = useMemo(
() => (
<StyledList>
{queuedTxsToDisplay.map((transaction) => (
<PendingTxListItem transaction={transaction.transaction} key={transaction.transaction.id} />
))}
</StyledList>
),
[queuedTxsToDisplay],
)

const getWidgetBody = () => {
if (loading) return LoadingState
if (!queuedTxsToDisplay.length) return <EmptyState />
return ResultState
}

return (
<WidgetContainer>
<StyledWidgetTitle>
<div className={css.title}>
<Typography component="h2" variant="subtitle1" fontWeight={700} mb={2}>
Transaction queue {totalQueuedTxs ? ` (${totalQueuedTxs})` : ''}
Pending transactions
</Typography>

{queuedTxns.length > 0 && <ViewAllLink url={queueUrl} />}
</StyledWidgetTitle>
<WidgetBody>{getWidgetBody()}</WidgetBody>
</div>

<WidgetBody>
{loading ? (
<LoadingState />
) : queuedTxns.length ? (
<div className={css.list}>
{txsToDisplay.map((tx) => (
<PendingTxListItem transaction={tx.transaction} key={tx.transaction.id} />
))}
</div>
) : (
<EmptyState />
)}
</WidgetBody>
</WidgetContainer>
)
}
Expand Down
17 changes: 17 additions & 0 deletions src/components/dashboard/PendingTxs/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Dashboard = (): ReactElement => {
</Grid>

<Grid item xs={12} lg={6}>
<PendingTxsList size={4} />
<PendingTxsList />
</Grid>

<Grid item xs={12} lg={supportsRelaying ? 6 : undefined}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/BatchExecuteButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const BatchExecuteButton = () => {
disabled={isDisabled}
onClick={handleOpenModal}
>
Bulk execute{isBatchable && ` ${batchableTransactions.length} txs`}
Bulk execute{isBatchable && ` ${batchableTransactions.length} transactions`}
</Button>
</span>
</Tooltip>
Expand Down
1 change: 1 addition & 0 deletions src/components/transactions/ExecuteTxButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const ExecuteTxButton = ({

const onClick = (e: SyntheticEvent) => {
e.stopPropagation()
e.preventDefault()
setTxFlow(<ConfirmTxFlow txSummary={txSummary} />, undefined, false)
}

Expand Down
1 change: 1 addition & 0 deletions src/components/transactions/SignTxButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const SignTxButton = ({

const onClick = (e: SyntheticEvent) => {
e.stopPropagation()
e.preventDefault()
setTxFlow(<ConfirmTxFlow txSummary={txSummary} />, undefined, false)
}

Expand Down

0 comments on commit 7637b32

Please sign in to comment.