Skip to content

Commit

Permalink
Refactor: a review screen for recovery attempts
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh committed Sep 18, 2024
1 parent 0617f63 commit ceb2aba
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 30 deletions.
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { type SyntheticEvent, useState, useContext, useCallback } from 'react'
import { CircularProgress, CardActions, Button, Typography, Stack, Divider } from '@mui/material'
import EthHashInfo from '@/components/common/EthHashInfo'
import CheckWallet from '@/components/common/CheckWallet'
import { Errors, trackError } from '@/services/exceptions'
import { dispatchRecoveryExecution } from '@/features/recovery/services/recovery-sender'
import useWallet from '@/hooks/wallets/useWallet'
import useSafeInfo from '@/hooks/useSafeInfo'
import type { TransactionAddedEvent } from '@gnosis.pm/zodiac/dist/cjs/types/Delay'
import ErrorMessage from '@/components/tx/ErrorMessage'
import TxCard from '@/components/tx-flow/common/TxCard'
import FieldsGrid from '@/components/tx/FieldsGrid'
import { TxModalContext } from '../..'

export type RecoveryAttemptReviewProps = {
params: {
args: TransactionAddedEvent.Log['args']
address: string
}
}

const RecoveryAttemptReview = ({ params }: RecoveryAttemptReviewProps) => {
const [isPending, setIsPending] = useState(false)
const [error, setError] = useState<Error>()
const wallet = useWallet()
const { safe } = useSafeInfo()
const { setTxFlow } = useContext(TxModalContext)

const onFormSubmit = useCallback(
async (e: SyntheticEvent) => {
e.preventDefault()

if (!wallet) return

setError(undefined)
setIsPending(true)

try {
await dispatchRecoveryExecution({
provider: wallet.provider,
chainId: safe.chainId,
args: params.args,
delayModifierAddress: params.address,
signerAddress: wallet.address,
})
setTxFlow(undefined)
} catch (err) {
trackError(Errors._812, err)
setError(err as Error)
}

setIsPending(false)
},
[wallet, safe, params, setTxFlow],
)

return (
<TxCard>
<form onSubmit={onFormSubmit}>
<Stack gap={3} mb={2}>
{params?.address && (
<FieldsGrid title="Initiated by">
<EthHashInfo address={params?.address} showAvatar hasExplorer showName />
</FieldsGrid>
)}
<Typography>Confirm or reject within the review time window.</Typography>
{error && <ErrorMessage error={error}>Error submitting the transaction.</ErrorMessage>}
</Stack>

<Divider sx={{ mx: -3, my: 3.5 }} />

<CardActions>
{/* Submit button, also available to non-owner role members */}
<CheckWallet allowNonOwner>
{(isOk) => (
<Button
data-testid="execute-through-role-form-btn"
variant="contained"
type="submit"
disabled={!isOk || isPending}
sx={{ minWidth: '112px' }}
>
{isPending ? <CircularProgress size={20} /> : 'Execute'}
</Button>
)}
</CheckWallet>
</CardActions>
</form>
</TxCard>
)
}

export default RecoveryAttemptReview
13 changes: 13 additions & 0 deletions src/components/tx-flow/flows/RecoveryAttempt/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import TxLayout from '@/components/tx-flow/common/TxLayout'
import SaveAddressIcon from '@/public/images/common/save-address.svg'
import RecoveryAttemptReview, { type RecoveryAttemptReviewProps } from './RecoveryAttemptReview'

const RecoveryAttemptFlow = ({ params }: { params: RecoveryAttemptReviewProps['params'] }) => {
return (
<TxLayout title="Recovery" subtitle="Confirm recovery" icon={SaveAddressIcon} step={0}>
<RecoveryAttemptReview params={params} />
</TxLayout>
)
}

export default RecoveryAttemptFlow
1 change: 1 addition & 0 deletions src/components/tx-flow/flows/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export const SuccessScreenFlow = dynamic(() => import('./SuccessScreen'))
export const TokenTransferFlow = dynamic(() => import('./TokenTransfer'))
export const UpdateSafeFlow = dynamic(() => import('./UpdateSafe'))
export const UpsertRecoveryFlow = dynamic(() => import('./UpsertRecovery'))
export const RecoveryAttemptFlow = dynamic(() => import('./RecoveryAttempt'))
73 changes: 73 additions & 0 deletions src/components/tx/SignOrExecuteForm/RecoveryExecuteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { type SyntheticEvent, useState } from 'react'
import { CircularProgress, CardActions, Button } from '@mui/material'
import CheckWallet from '@/components/common/CheckWallet'
import { Errors, trackError } from '@/services/exceptions'
import { dispatchRecoveryExecution } from '@/features/recovery/services/recovery-sender'
import useWallet from '@/hooks/wallets/useWallet'
import useSafeInfo from '@/hooks/useSafeInfo'
import type { TransactionAddedEvent } from '@gnosis.pm/zodiac/dist/cjs/types/Delay'
import ErrorMessage from '../ErrorMessage'

export type RecoveryExecuteFormProps = {
params: {
args: TransactionAddedEvent.Log['args']
address: string
}
onSubmit: (txId: string, isExecuted?: boolean) => void
}

const RecoveryExecuteForm = ({ params, onSubmit }: RecoveryExecuteFormProps) => {
const [isPending, setIsPending] = useState(false)
const [error, setError] = useState<Error>()
const wallet = useWallet()
const { safe } = useSafeInfo()

const onFormSubmit = async (e: SyntheticEvent) => {
e.preventDefault()

if (!wallet) return

setIsPending(true)

try {
await dispatchRecoveryExecution({
provider: wallet.provider,
chainId: safe.chainId,
args: params.args,
delayModifierAddress: params.address,
signerAddress: wallet.address,
})
onSubmit('', true)
} catch (err) {
trackError(Errors._812, err)
setError(err as Error)
}

setIsPending(false)
}

return (
<form onSubmit={onFormSubmit}>
<ErrorMessage error={error}>Error submitting the transaction.</ErrorMessage>

<CardActions>
{/* Submit button, also available to non-owner role members */}
<CheckWallet allowNonOwner>
{(isOk) => (
<Button
data-testid="execute-through-role-form-btn"
variant="contained"
type="submit"
disabled={!isOk}
sx={{ minWidth: '112px' }}
>
{isPending ? <CircularProgress size={20} /> : 'Execute'}
</Button>
)}
</CheckWallet>
</CardActions>
</form>
)
}

export default RecoveryExecuteForm
33 changes: 4 additions & 29 deletions src/features/recovery/components/ExecuteRecoveryButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import useWallet from '@/hooks/wallets/useWallet'
import { Button, Tooltip } from '@mui/material'
import { useContext } from 'react'
import type { SyntheticEvent, ReactElement } from 'react'

import CheckWallet from '@/components/common/CheckWallet'
import { dispatchRecoveryExecution } from '@/features/recovery/services/recovery-sender'
import useOnboard from '@/hooks/wallets/useOnboard'
import useSafeInfo from '@/hooks/useSafeInfo'
import { useRecoveryTxState } from '@/features/recovery/hooks/useRecoveryTxState'
import { Errors, trackError } from '@/services/exceptions'
import { asError } from '@/services/exceptions/utils'
import { RecoveryListItemContext } from '../RecoveryListItem/RecoveryListItemContext'
import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-state'
import useIsWrongChain from '@/hooks/useIsWrongChain'
import { useCurrentChain } from '@/hooks/useChains'
import { TxModalContext } from '@/components/tx-flow'
import { RecoveryAttemptFlow } from '@/components/tx-flow/flows'

export function ExecuteRecoveryButton({
recovery,
Expand All @@ -22,37 +17,17 @@ export function ExecuteRecoveryButton({
recovery: RecoveryQueueItem
compact?: boolean
}): ReactElement {
const { setSubmitError } = useContext(RecoveryListItemContext)
const { isExecutable, isNext, isPending } = useRecoveryTxState(recovery)
const onboard = useOnboard()
const wallet = useWallet()
const { safe } = useSafeInfo()
const isDisabled = !isExecutable || isPending
const isWrongChain = useIsWrongChain()
const chain = useCurrentChain()
const { setTxFlow } = useContext(TxModalContext)

const onClick = async (e: SyntheticEvent) => {
e.stopPropagation()
e.preventDefault()

if (!onboard || !wallet) {
return
}

try {
await dispatchRecoveryExecution({
provider: wallet.provider,
chainId: safe.chainId,
args: recovery.args,
delayModifierAddress: recovery.address,
signerAddress: wallet.address,
})
} catch (_err) {
const err = asError(_err)

trackError(Errors._812, e)
setSubmitError(err)
}
setTxFlow(<RecoveryAttemptFlow params={recovery} />)
}

return (
Expand Down

0 comments on commit ceb2aba

Please sign in to comment.