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
1 parent 6dcfc49 commit 338d61e
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 76 deletions.
32 changes: 23 additions & 9 deletions src/components/dashboard/Recovery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ 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 { useRouter } from 'next/router'
import { AppRoutes } from '@/config/routes'
import CheckWallet from '@/components/common/CheckWallet'
import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@/utils/chains'

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

export function Recovery(): ReactElement {
const router = useRouter()
const { setTxFlow } = useContext(TxModalContext)
const recovery = useAppSelector(selectRecovery)
const supportsRecovery = useHasFeature(FEATURES.RECOVERY)

const onEnable = () => {
setTxFlow(<UpsertRecoveryFlow />)
Expand Down Expand Up @@ -52,14 +56,24 @@ 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>
{recovery.length === 0 ? (
<Button variant="contained" onClick={onEnable}>
Set up recovery
</Button>
) : (
<Button variant="contained" onClick={onEdit}>
Edit recovery setup
</Button>
{supportsRecovery && (
<CheckWallet>
{(isOk) => {
if (recovery.length === 0) {
return (
<Button variant="contained" disabled={!isOk} onClick={onEnable}>
Set up recovery
</Button>
)
}

return (
<Button variant="contained" disabled={!isOk} onClick={onEdit}>
Edit recovery
</Button>
)
}}
</CheckWallet>
)}
</Grid>
</Grid>
Expand Down
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} />
</>
)
}
54 changes: 20 additions & 34 deletions src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
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 { 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 { 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 CheckWallet from '@/components/common/CheckWallet'

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

Expand Down Expand Up @@ -75,7 +74,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 +110,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 @@ -170,9 +147,18 @@ export function Recovery(): ReactElement {
Give us feedback
</ExternalLink>
</Alert>
<Button variant="contained" onClick={() => setTxFlow(<UpsertRecoveryFlow />)} sx={{ mt: 2 }}>
Set up recovery
</Button>
<CheckWallet>
{(isOk) => (
<Button
variant="contained"
disabled={!isOk}
onClick={() => setTxFlow(<UpsertRecoveryFlow />)}
sx={{ mt: 2 }}
>
Set up recovery
</Button>
)}
</CheckWallet>
</>
) : (
<EnhancedTable rows={rows} headCells={headCells} />
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
Loading

0 comments on commit 338d61e

Please sign in to comment.