Skip to content

Commit

Permalink
feat: recovery module removal
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Nov 22, 2023
1 parent b06d1e4 commit 8919c56
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 74 deletions.
74 changes: 74 additions & 0 deletions src/components/settings/Recovery/ConfirmRemoveRecoveryModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
IconButton,
SvgIcon,
} from '@mui/material'
import CloseIcon from '@mui/icons-material/Close'
import { useContext } from 'react'
import type { ReactElement } from 'react'

import AlertIcon from '@/public/images/notifications/alert.svg'
import { TxModalContext } from '@/components/tx-flow'
import { RemoveRecoveryFlow } from '@/components/tx-flow/flows/RemoveRecovery'
import type { RecoveryState } from '@/store/recoverySlice'

export function ConfirmRemoveRecoveryModal({
open,
onClose,
delayModifier,
}: {
open: boolean
onClose: () => void
delayModifier: RecoveryState[number]
}): ReactElement {
const { setTxFlow } = useContext(TxModalContext)

const onConfirm = () => {
setTxFlow(<RemoveRecoveryFlow delayModifier={delayModifier} />)
onClose()
}

return (
<Dialog open={open} onClose={onClose}>
<DialogTitle display="flex" alignItems="center" sx={{ pt: 3 }}>
<SvgIcon
component={AlertIcon}
inheritViewBox
sx={{
color: (theme) => theme.palette.error.main,
mr: '10px',
}}
/>
Remove the recovery module?
<IconButton
onClick={onClose}
sx={{
color: (theme) => theme.palette.text.secondary,
ml: 'auto',
}}
>
<CloseIcon />
</IconButton>
</DialogTitle>

<DialogContent dividers sx={{ py: 2, px: 3 }}>
<DialogContentText color="text.primary">
Are you sure you wish to remove the recovery module? The assigned guardian won&apos;t be able to recover this
Safe account for you.
</DialogContentText>
</DialogContent>

<DialogActions sx={{ display: 'flex', justifyContent: 'space-between', p: 3, pb: 2 }}>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={onConfirm} autoFocus variant="danger">
Remove
</Button>
</DialogActions>
</Dialog>
)
}
62 changes: 62 additions & 0 deletions src/components/settings/Recovery/DelayModifierRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { IconButton, SvgIcon, Tooltip } from '@mui/material'
import { useContext, useState } from 'react'
import type { ReactElement } from 'react'

import { TxModalContext } from '@/components/tx-flow'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import DeleteIcon from '@/public/images/common/delete.svg'
import EditIcon from '@/public/images/common/edit.svg'
import CheckWallet from '@/components/common/CheckWallet'
import { ConfirmRemoveRecoveryModal } from './ConfirmRemoveRecoveryModal'
import type { RecoveryState } from '@/store/recoverySlice'

export function DelayModifierRow({ delayModifier }: { delayModifier: RecoveryState[number] }): ReactElement | null {
const { setTxFlow } = useContext(TxModalContext)
const isOwner = useIsSafeOwner()
const [confirm, setConfirm] = useState(false)

if (!isOwner) {
return null
}

const onEdit = () => {
// TODO: Display flow
setTxFlow(undefined)
}

const onDelete = () => {
setConfirm(true)
}

const onCloseConfirm = () => {
setConfirm(false)
}

return (
<>
<CheckWallet>
{(isOk) => (
<>
<Tooltip title={isOk ? 'Edit recovery setup' : undefined}>
<span>
<IconButton onClick={onEdit} size="small" disabled={!isOk}>
<SvgIcon component={EditIcon} inheritViewBox color="border" fontSize="small" />
</IconButton>
</span>
</Tooltip>

<Tooltip title={isOk ? 'Disable recovery' : undefined}>
<span>
<IconButton onClick={onDelete} size="small" disabled={!isOk}>
<SvgIcon component={DeleteIcon} inheritViewBox color="error" fontSize="small" />
</IconButton>
</span>
</Tooltip>
</>
)}
</CheckWallet>

<ConfirmRemoveRecoveryModal open={confirm} onClose={onCloseConfirm} delayModifier={delayModifier} />
</>
)
}
47 changes: 8 additions & 39 deletions src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { Alert, Box, Button, Grid, IconButton, Paper, SvgIcon, Tooltip, Typography } from '@mui/material'
import { Alert, Box, Button, Grid, Paper, SvgIcon, Tooltip, Typography } from '@mui/material'
import { useContext, useMemo } from 'react'
import type { ReactElement } from 'react'

import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery'
import { TxModalContext } from '@/components/tx-flow'
import { Chip } from '@/components/common/Chip'
import ExternalLink from '@/components/common/ExternalLink'
import { RecoverAccountFlow } from '@/components/tx-flow/flows/RecoverAccount'
import { DelayModifierRow } from './DelayModifierRow'
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'
Expand Down Expand Up @@ -75,7 +72,9 @@ export function Recovery(): ReactElement {
const isOwner = useIsSafeOwner()

const rows = useMemo(() => {
return recovery.flatMap(({ guardians, txCooldown, txExpiration }) => {
return recovery.flatMap((delayModifier) => {
const { guardians, txCooldown, txExpiration } = delayModifier

return guardians.map((guardian) => {
const DAY_IN_SECONDS = 60 * 60 * 24

Expand Down Expand Up @@ -109,39 +108,15 @@ export function Recovery(): ReactElement {
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>
)}
<DelayModifierRow delayModifier={delayModifier} />
</div>
),
},
},
}
})
})
}, [recovery, isOwner, setTxFlow])
}, [recovery])

return (
<Paper sx={{ p: 4 }}>
Expand Down Expand Up @@ -175,13 +150,7 @@ export function Recovery(): ReactElement {
</Button>
</>
) : (
<>
<EnhancedTable rows={rows} headCells={headCells} />
{/* TODO: Move to correct location when widget is ready */}
<Button variant="contained" onClick={() => setTxFlow(<RecoverAccountFlow />)}>
Propose recovery
</Button>
</>
<EnhancedTable rows={rows} headCells={headCells} />
)}
</Grid>
</Grid>
Expand Down
64 changes: 40 additions & 24 deletions src/components/settings/SafeModules/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import ExternalLink from '@/components/common/ExternalLink'
import RemoveModuleFlow from '@/components/tx-flow/flows/RemoveModule'
import DeleteIcon from '@/public/images/common/delete.svg'
import CheckWallet from '@/components/common/CheckWallet'
import { useContext } from 'react'
import { useContext, useState } from 'react'
import { TxModalContext } from '@/components/tx-flow'
import { useAppSelector } from '@/store'
import { selectDelayModifierByAddress } from '@/store/recoverySlice'
import { ConfirmRemoveRecoveryModal } from '../Recovery/ConfirmRemoveRecoveryModal'
import css from '../TransactionGuards/styles.module.css'

const NoModules = () => {
Expand All @@ -20,31 +23,44 @@ const NoModules = () => {

const ModuleDisplay = ({ moduleAddress, chainId, name }: { moduleAddress: string; chainId: string; name?: string }) => {
const { setTxFlow } = useContext(TxModalContext)
const [confirmRemoveRecovery, setConfirmRemoveRecovery] = useState(false)
const delayModifier = useAppSelector((state) => selectDelayModifierByAddress(state, moduleAddress))

const onRemove = () => {
if (delayModifier) {
setConfirmRemoveRecovery(true)
} else {
setTxFlow(<RemoveModuleFlow address={moduleAddress} />)
}
}

return (
<Box className={css.guardDisplay}>
<EthHashInfo
name={name}
shortAddress={false}
address={moduleAddress}
showCopyButton
chainId={chainId}
hasExplorer
/>
<CheckWallet>
{(isOk) => (
<IconButton
onClick={() => setTxFlow(<RemoveModuleFlow address={moduleAddress} />)}
color="error"
size="small"
disabled={!isOk}
title="Remove module"
>
<SvgIcon component={DeleteIcon} inheritViewBox color="error" fontSize="small" />
</IconButton>
)}
</CheckWallet>
</Box>
<>
<Box className={css.guardDisplay}>
<EthHashInfo
name={name}
shortAddress={false}
address={moduleAddress}
showCopyButton
chainId={chainId}
hasExplorer
/>
<CheckWallet>
{(isOk) => (
<IconButton onClick={onRemove} color="error" size="small" disabled={!isOk} title="Remove module">
<SvgIcon component={DeleteIcon} inheritViewBox color="error" fontSize="small" />
</IconButton>
)}
</CheckWallet>
</Box>
{delayModifier && (
<ConfirmRemoveRecoveryModal
open={confirmRemoveRecovery}
onClose={() => setConfirmRemoveRecovery(false)}
delayModifier={delayModifier}
/>
)}
</>
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/sidebar/SidebarNavigation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import useSafeInfo from '@/hooks/useSafeInfo'
import { AppRoutes } from '@/config/routes'
import useTxQueue from '@/hooks/useTxQueue'
import { useAppSelector } from '@/store'
import { selectAllRecoveryQueues } from '@/store/recoverySlice'
import { selectRecoveryQueues } from '@/store/recoverySlice'

const getSubdirectory = (pathname: string): string => {
return pathname.split('/')[1]
Expand All @@ -25,7 +25,7 @@ const Navigation = (): ReactElement => {
const { safe } = useSafeInfo()
const currentSubdirectory = getSubdirectory(router.pathname)
const hasQueuedTxs = Boolean(useTxQueue().page?.results.length)
const hasRecoveryTxs = Boolean(useAppSelector(selectAllRecoveryQueues).length)
const hasRecoveryTxs = Boolean(useAppSelector(selectRecoveryQueues).length)

// Indicate whether the current Safe needs an upgrade
const setupItem = navItems.find((item) => item.href === AppRoutes.settings.setup)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Paper, Typography, SvgIcon } from '@mui/material'
import type { SxProps } from '@mui/material'
import type { AddressEx } from '@safe-global/safe-gateway-typescript-sdk'
import type { ReactElement } from 'react'

Expand All @@ -7,14 +8,22 @@ import EthHashInfo from '@/components/common/EthHashInfo'

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

export function NewOwnerList({ newOwners }: { newOwners: Array<AddressEx> }): ReactElement {
export function OwnerList({
title,
owners,
sx,
}: {
owners: Array<AddressEx>
title?: string
sx?: SxProps
}): ReactElement {
return (
<Paper className={css.container}>
<Paper className={css.container} sx={sx}>
<Typography color="text.secondary" display="flex" alignItems="center">
<SvgIcon component={PlusIcon} inheritViewBox fontSize="small" sx={{ mr: 1 }} />
New owner{newOwners.length > 1 ? 's' : ''}
{title ?? `New owner{owners.length > 1 ? 's' : ''}`}
</Typography>
{newOwners.map((newOwner) => (
{owners.map((newOwner) => (
<EthHashInfo
key={newOwner.value}
address={newOwner.value}
Expand Down
4 changes: 2 additions & 2 deletions src/components/tx-flow/flows/AddOwner/ReviewOwner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { upsertAddressBookEntry } from '@/store/addressBookSlice'
import { SafeTxContext } from '../../SafeTxProvider'
import type { AddOwnerFlowProps } from '.'
import type { ReplaceOwnerFlowProps } from '../ReplaceOwner'
import { NewOwnerList } from '../../common/NewOwnerList'
import { OwnerList } from '../../common/OwnerList'
import MinusIcon from '@/public/images/common/minus.svg'
import EthHashInfo from '@/components/common/EthHashInfo'
import commonCss from '@/components/tx-flow/common/styles.module.css'
Expand Down Expand Up @@ -68,7 +68,7 @@ export const ReviewOwner = ({ params }: { params: AddOwnerFlowProps | ReplaceOwn
/>
</Paper>
)}
<NewOwnerList newOwners={[{ name: newOwner.name, value: newOwner.address }]} />
<OwnerList owners={[{ name: newOwner.name, value: newOwner.address }]} />
<Divider className={commonCss.nestedDivider} />
<Box>
<Typography variant="body2">Any transaction requires the confirmation of:</Typography>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { SafeTxContext } from '../../SafeTxProvider'
import CheckWallet from '@/components/common/CheckWallet'
import { createMultiSendCallOnlyTx, createTx, dispatchRecoveryProposal } from '@/services/tx/tx-sender'
import { RecoverAccountFlowFields } from '.'
import { NewOwnerList } from '../../common/NewOwnerList'
import { OwnerList } from '../../common/OwnerList'
import { useAppSelector } from '@/store'
import { selectDelayModifierByGuardian } from '@/store/recoverySlice'
import useWallet from '@/hooks/wallets/useWallet'
Expand Down Expand Up @@ -93,7 +93,7 @@ export function RecoverAccountFlowReview({ params }: { params: RecoverAccountFlo
{newThreshold !== safe.threshold ? ' and threshold' : ''}.
</Typography>

<NewOwnerList newOwners={newOwners} />
<OwnerList owners={newOwners} />

<Divider className={commonCss.nestedDivider} sx={{ mt: 'var(--space-2) !important' }} />

Expand Down
Loading

0 comments on commit 8919c56

Please sign in to comment.