Skip to content

Commit

Permalink
Merge branch 'recovery-epic' into edit-recovery-flow
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Nov 22, 2023
2 parents 5370666 + b06d1e4 commit e90ffa6
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 86 deletions.
2 changes: 1 addition & 1 deletion src/components/common/EnhancedTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type EnhancedRow = {

type EnhancedHeadCell = {
id: string
label: string
label: ReactNode
width?: string
sticky?: boolean
}
Expand Down
27 changes: 23 additions & 4 deletions src/components/dashboard/Recovery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,29 @@ import { WidgetBody, WidgetContainer } from '@/components/dashboard/styled'
import { Chip } from '@/components/common/Chip'
import { TxModalContext } from '@/components/tx-flow'
import { UpsertRecoveryFlow } from '@/components/tx-flow/flows/UpsertRecovery'
import { useAppSelector } from '@/store'
import { selectRecovery } from '@/store/recoverySlice'
import { useRouter } from 'next/dist/client/router'
import { AppRoutes } from '@/config/routes'

import css from './styles.module.css'

export function Recovery(): ReactElement {
const router = useRouter()
const { setTxFlow } = useContext(TxModalContext)
const recovery = useAppSelector(selectRecovery)

const onClick = () => {
const onEnable = () => {
setTxFlow(<UpsertRecoveryFlow />)
}

const onEdit = () => {
router.push({
pathname: AppRoutes.settings.recovery,
query: router.query,
})
}

return (
<WidgetContainer>
<Typography component="h2" variant="subtitle1" className={css.label}>
Expand All @@ -39,9 +52,15 @@ export function Recovery(): ReactElement {
<Typography mt={1} mb={3}>
Ensure that you never lose access to your funds by choosing a guardian to recover your account.
</Typography>
<Button variant="contained" onClick={onClick}>
Set up recovery
</Button>
{recovery.length === 0 ? (
<Button variant="contained" onClick={onEnable}>
Set up recovery
</Button>
) : (
<Button variant="contained" onClick={onEdit}>
Edit recovery setup
</Button>
)}
</Grid>
</Grid>
</Card>
Expand Down
174 changes: 144 additions & 30 deletions src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,147 @@
import { Alert, Box, Button, Grid, Paper, Typography } from '@mui/material'
import { useContext } from 'react'
import { Alert, Box, Button, Grid, IconButton, Paper, SvgIcon, Tooltip, Typography } from '@mui/material'
import { useContext, useMemo } from 'react'
import type { ReactElement } from 'react'

import { UpsertRecoveryFlow } from '@/components/tx-flow/flows/UpsertRecovery'
import { TxModalContext } from '@/components/tx-flow'
import { Chip } from '@/components/common/Chip'
import ExternalLink from '@/components/common/ExternalLink'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import { useAppSelector } from '@/store'
import { selectRecovery } from '@/store/recoverySlice'
import EthHashInfo from '@/components/common/EthHashInfo'
import DeleteIcon from '@/public/images/common/delete.svg'
import EditIcon from '@/public/images/common/edit.svg'
import EnhancedTable from '@/components/common/EnhancedTable'
import CheckWallet from '@/components/common/CheckWallet'
import InfoIcon from '@/public/images/notifications/info.svg'

import tableCss from '@/components/common/EnhancedTable/styles.module.css'

enum HeadCells {
Guardian = 'guardian',
TxCooldown = 'txCooldown',
TxExpiration = 'txExpiration',
Actions = 'actions',
}

const headCells = [
{ id: HeadCells.Guardian, label: 'Guardian' },
{
id: HeadCells.TxCooldown,
label: (
<>
Recovery delay{' '}
<Tooltip title="You can cancel any recovery attempt when it is not needed or wanted within the delay period.">
<span>
<SvgIcon
component={InfoIcon}
inheritViewBox
color="border"
fontSize="small"
sx={{ verticalAlign: 'middle', ml: 0.5 }}
/>
</span>
</Tooltip>
</>
),
},
{
id: HeadCells.TxExpiration,
label: (
<>
Expiry{' '}
<Tooltip title="A period of time after which the recovery attempt will expire and can no longer be executed.">
<span>
<SvgIcon
component={InfoIcon}
inheritViewBox
color="border"
fontSize="small"
sx={{ verticalAlign: 'middle', ml: 0.5 }}
/>
</span>
</Tooltip>
</>
),
},
{ id: HeadCells.Actions, label: '', sticky: true },
]

// TODO: Migrate section
export function Recovery(): ReactElement {
const { setTxFlow } = useContext(TxModalContext)
const recovery = useAppSelector(selectRecovery)
const isOwner = useIsSafeOwner()

const rows = useMemo(() => {
return recovery.flatMap(({ guardians, txCooldown, txExpiration }) => {
return guardians.map((guardian) => {
const DAY_IN_SECONDS = 60 * 60 * 24

const txCooldownDays = txCooldown.div(DAY_IN_SECONDS).toNumber()
const txExpirationDays = txExpiration.div(DAY_IN_SECONDS).toNumber()

return {
cells: {
[HeadCells.Guardian]: {
rawValue: guardian,
content: <EthHashInfo address={guardian} showCopyButton hasExplorer />,
},
[HeadCells.TxCooldown]: {
rawValue: txCooldownDays,
content: (
<Typography>
{txCooldownDays} day{txCooldownDays > 1 ? 's' : ''}
</Typography>
),
},
[HeadCells.TxExpiration]: {
rawValue: txExpirationDays,
content: (
<Typography>
{txExpirationDays === 0 ? 'never' : `${txExpirationDays} day${txExpirationDays > 1 ? 's' : ''}`}
</Typography>
),
},
[HeadCells.Actions]: {
rawValue: '',
sticky: true,
content: (
<div className={tableCss.actions}>
{isOwner && (
<CheckWallet>
{(isOk) => (
<>
<Tooltip title={isOk ? 'Edit recovery setup' : undefined}>
<span>
{/* TODO: Display flow */}
<IconButton onClick={() => setTxFlow(undefined)} size="small" disabled={!isOk}>
<SvgIcon component={EditIcon} inheritViewBox color="border" fontSize="small" />
</IconButton>
</span>
</Tooltip>

<Tooltip title={isOk ? 'Disable recovery' : undefined}>
<span>
{/* TODO: Display flow */}
<IconButton onClick={() => setTxFlow(undefined)} size="small" disabled={!isOk}>
<SvgIcon component={DeleteIcon} inheritViewBox color="error" fontSize="small" />
</IconButton>
</span>
</Tooltip>
</>
)}
</CheckWallet>
)}
</div>
),
},
},
}
})
})
}, [recovery, isOwner, setTxFlow])

return (
<Paper sx={{ p: 4 }}>
Expand All @@ -33,36 +162,21 @@ export function Recovery(): ReactElement {
Enabling the Account recovery module will require a transactions.
</Typography>

<Alert severity="info">
Unhappy with the provided option? {/* TODO: Add link */}
<ExternalLink noIcon href="#">
Give us feedback
</ExternalLink>
</Alert>

<Box mt={2}>
{recovery ? (
<Button
variant="contained"
onClick={() =>
setTxFlow(
<UpsertRecoveryFlow
recovery={
// TODO: Change to selected module when implementing https://github.com/safe-global/safe-wallet-web/issues/2756
recovery[0]
}
/>,
)
}
>
Edit recovery
</Button>
) : (
<Button variant="contained" onClick={() => setTxFlow(<UpsertRecoveryFlow />)}>
{recovery.length === 0 ? (
<>
<Alert severity="info">
Unhappy with the provided option? {/* TODO: Add link */}
<ExternalLink noIcon href="#">
Give us feedback
</ExternalLink>
</Alert>
<Button variant="contained" onClick={() => setTxFlow(<UpsertRecoveryFlow />)} sx={{ mt: 2 }}>
Set up recovery
</Button>
)}
</Box>
</>
) : (
<EnhancedTable rows={rows} headCells={headCells} />
)}
</Grid>
</Grid>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
import { Errors, logError } from '@/services/exceptions'
import { createMultiSendCallOnlyTx, createTx } from '@/services/tx/tx-sender'
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
import { getEditRecoveryTransactions, getRecoverySetupTransactions } from '@/services/recovery/setup'
import { getRecoveryUpsertTransactions } from '@/services/recovery/setup'
import { useWeb3 } from '@/hooks/wallets/web3'
import useSafeInfo from '@/hooks/useSafeInfo'
import { SvgIcon, Tooltip, Typography } from '@mui/material'
Expand All @@ -14,43 +14,6 @@ import { TxDataRow } from '@/components/transactions/TxDetails/Summary/TxDataRow
import InfoIcon from '@/public/images/notifications/info.svg'
import EthHashInfo from '@/components/common/EthHashInfo'
import type { UpsertRecoveryFlowProps } from '.'
import type { Web3Provider } from '@ethersproject/providers'

const getSafeTx = async ({
txCooldown,
txExpiration,
guardian,
provider,
moduleAddress,
chainId,
safeAddress,
}: UpsertRecoveryFlowProps & {
moduleAddress?: string
provider: Web3Provider
chainId: string
safeAddress: string
}) => {
if (moduleAddress) {
return getEditRecoveryTransactions({
moduleAddress,
newTxCooldown: txCooldown,
newTxExpiration: txExpiration,
newGuardians: [guardian],
provider,
})
}

const { transactions } = getRecoverySetupTransactions({
txCooldown,
txExpiration,
guardians: [guardian],
chainId,
safeAddress,
provider,
})

return transactions
}

export function UpsertRecoveryFlowReview({
params,
Expand All @@ -75,7 +38,7 @@ export function UpsertRecoveryFlowReview({
return
}

getSafeTx({
getRecoveryUpsertTransactions({
...params,
provider: web3,
chainId: safe.chainId,
Expand Down
16 changes: 8 additions & 8 deletions src/services/recovery/__tests__/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { OperationType } from '@safe-global/safe-core-sdk-types'
import { SENTINEL_ADDRESS } from '@safe-global/safe-core-sdk/dist/src/utils/constants'
import type { Web3Provider } from '@ethersproject/providers'

import { getEditRecoveryTransactions, getRecoverySetupTransactions } from '@/services/recovery/setup'
import { _getEditRecoveryTransactions, _getRecoverySetupTransactions } from '@/services/recovery/setup'

jest.mock('@gnosis.pm/zodiac', () => ({
...jest.requireActual('@gnosis.pm/zodiac'),
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('getRecoverySetupTransactions', () => {
transaction: deployDelayModifierTx,
})

const result = getRecoverySetupTransactions({
const result = _getRecoverySetupTransactions({
txCooldown,
txExpiration,
guardians,
Expand Down Expand Up @@ -115,7 +115,7 @@ describe('getEditRecoveryTransactions', () => {
},
} as any)

const transactions = await getEditRecoveryTransactions({
const transactions = await _getEditRecoveryTransactions({
provider: {} as Web3Provider,
newTxCooldown: txCooldown,
newTxExpiration,
Expand Down Expand Up @@ -155,7 +155,7 @@ describe('getEditRecoveryTransactions', () => {
},
} as any)

const transactions = await getEditRecoveryTransactions({
const transactions = await _getEditRecoveryTransactions({
provider: {} as Web3Provider,
newTxCooldown,
newTxExpiration: txExpiration,
Expand Down Expand Up @@ -195,7 +195,7 @@ describe('getEditRecoveryTransactions', () => {
},
} as any)

const transactions = await getEditRecoveryTransactions({
const transactions = await _getEditRecoveryTransactions({
provider: {} as Web3Provider,
newTxCooldown: txCooldown,
newTxExpiration: txExpiration,
Expand Down Expand Up @@ -241,7 +241,7 @@ describe('getEditRecoveryTransactions', () => {
},
} as any)

const transactions = await getEditRecoveryTransactions({
const transactions = await _getEditRecoveryTransactions({
provider: {} as Web3Provider,
newTxCooldown: txCooldown,
newTxExpiration: txExpiration,
Expand Down Expand Up @@ -285,7 +285,7 @@ describe('getEditRecoveryTransactions', () => {
},
} as any)

const transactions = await getEditRecoveryTransactions({
const transactions = await _getEditRecoveryTransactions({
provider: {} as Web3Provider,
newTxCooldown,
newTxExpiration,
Expand Down Expand Up @@ -344,7 +344,7 @@ describe('getEditRecoveryTransactions', () => {
},
} as any)

const transactions = await getEditRecoveryTransactions({
const transactions = await _getEditRecoveryTransactions({
provider: {} as Web3Provider,
newTxCooldown: txCooldown,
newTxExpiration: txExpiration,
Expand Down
Loading

0 comments on commit e90ffa6

Please sign in to comment.