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: recovery module removal #2848

Merged
merged 2 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
Loading