Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: actionable pending txs on the Dashboard #2523

Merged
merged 6 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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