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: edit recovery flow #2824

Merged
merged 13 commits into from
Nov 23, 2023
Merged
46 changes: 34 additions & 12 deletions src/components/dashboard/Recovery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,31 @@ import RecoveryLogo from '@/public/images/common/recovery.svg'
import { WidgetBody, WidgetContainer } from '@/components/dashboard/styled'
import { Chip } from '@/components/common/Chip'
import { TxModalContext } from '@/components/tx-flow'
import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery'
import { useIsRecoveryEnabled } from '@/hooks/useIsRecoveryEnabled'
import { UpsertRecoveryFlow } from '@/components/tx-flow/flows/UpsertRecovery'
import { useRecovery } from '@/components/recovery/RecoveryContext'
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 isRecoveryEnabled = useIsRecoveryEnabled()
const router = useRouter()
const { setTxFlow } = useContext(TxModalContext)
const [recovery] = useRecovery()
const supportsRecovery = useHasFeature(FEATURES.RECOVERY)

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

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

return (
Expand All @@ -42,14 +55,23 @@ 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>

{isRecoveryEnabled && (
{supportsRecovery && (
<CheckWallet>
{(isOk) => (
<Button variant="contained" disabled={!isOk} onClick={onClick}>
Set up recovery
</Button>
)}
{(isOk) => {
if (!recovery || 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>
Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/Recovery/DelayModifierRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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 { UpsertRecoveryFlow } from '@/components/tx-flow/flows/UpsertRecovery'
import type { RecoveryStateItem } from '@/components/recovery/RecoveryContext'

export function DelayModifierRow({ delayModifier }: { delayModifier: RecoveryStateItem }): ReactElement | null {
Expand All @@ -20,8 +21,7 @@ export function DelayModifierRow({ delayModifier }: { delayModifier: RecoverySta
}

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

const onDelete = () => {
Expand Down
5 changes: 3 additions & 2 deletions src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Alert, Box, Button, Grid, Paper, SvgIcon, Tooltip, Typography } from '@
import { useContext, useMemo } from 'react'
import type { ReactElement } from 'react'

import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery'
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'
Expand Down Expand Up @@ -65,6 +65,7 @@ const headCells = [
{ id: HeadCells.Actions, label: '', sticky: true },
]

// TODO: Combine section with spending limits under "Security & Login" as per design
export function Recovery(): ReactElement {
const { setTxFlow } = useContext(TxModalContext)
const [recovery] = useRecovery()
Expand Down Expand Up @@ -148,7 +149,7 @@ export function Recovery(): ReactElement {
<Button
variant="contained"
disabled={!isOk}
onClick={() => setTxFlow(<EnableRecoveryFlow />)}
onClick={() => setTxFlow(<UpsertRecoveryFlow />)}
sx={{ mt: 2 }}
>
Set up recovery
Expand Down
2 changes: 1 addition & 1 deletion src/components/settings/SafeModules/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import DeleteIcon from '@/public/images/common/delete.svg'
import CheckWallet from '@/components/common/CheckWallet'
import { useContext, useState } from 'react'
import { TxModalContext } from '@/components/tx-flow'
import { useRecovery } from '@/components/recovery/RecoveryContext'
import { selectDelayModifierByAddress } from '@/services/recovery/selectors'
import { ConfirmRemoveRecoveryModal } from '../Recovery/ConfirmRemoveRecoveryModal'
import { useRecovery } from '@/components/recovery/RecoveryContext'

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

Expand Down
4 changes: 2 additions & 2 deletions src/components/tx-flow/common/TxLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SafeLogo from '@/public/images/logo-no-text.svg'
import { TxSecurityProvider } from '@/components/tx/security/shared/TxSecurityContext'
import ChainIndicator from '@/components/common/ChainIndicator'
import SecurityWarnings from '@/components/tx/security/SecurityWarnings'
import { EnableRecoveryFlowEmailHint } from '../../flows/EnableRecovery/EnableRecoveryFlowEmailHint'
import { UpsertRecoveryFlowEmailHint } from '../../flows/UpsertRecovery/UpsertRecoveryFlowEmailHint'

const TxLayoutHeader = ({
hideNonce,
Expand Down Expand Up @@ -158,7 +158,7 @@ const TxLayout = ({
<Box className={css.sticky}>
<SecurityWarnings />

{isRecovery && <EnableRecoveryFlowEmailHint />}
{isRecovery && <UpsertRecoveryFlowEmailHint />}
</Box>
</Grid>
</Grid>
Expand Down
91 changes: 0 additions & 91 deletions src/components/tx-flow/flows/EnableRecovery/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import LightbulbIcon from '@/public/images/common/lightbulb.svg'

import infoWidgetCss from '@/components/new-safe/create/InfoWidget/styles.module.css'

export function EnableRecoveryFlowEmailHint(): ReactElement {
export function UpsertRecoveryFlowEmailHint(): ReactElement {
return (
<Alert severity="info" sx={{ border: 'unset', p: 3 }} icon={false}>
<Box className={infoWidgetCss.title} sx={{ backgroundColor: ({ palette }) => palette.info.main }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const RecoverySteps = [
},
]

export function EnableRecoveryFlowIntro({ onSubmit }: { onSubmit: () => void }): ReactElement {
export function UpsertRecoveryFlowIntro({ onSubmit }: { onSubmit: () => void }): ReactElement {
return (
<TxCard>
<Grid container display="flex" gap={4} className={css.connector}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,55 @@ import type { ReactElement } from 'react'

import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm'
import { Errors, logError } from '@/services/exceptions'
import { createMultiSendCallOnlyTx } from '@/services/tx/tx-sender'
import { createMultiSendCallOnlyTx, createTx } from '@/services/tx/tx-sender'
import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
import { getRecoverySetup } 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'
import { EnableRecoveryFlowFields, RecoveryDelayPeriods, RecoveryExpirationPeriods } from '.'
import { useRecoveryPeriods } from './useRecoveryPeriods'
import { TxDataRow } from '@/components/transactions/TxDetails/Summary/TxDataRow'
import InfoIcon from '@/public/images/notifications/info.svg'
import EthHashInfo from '@/components/common/EthHashInfo'
import type { EnableRecoveryFlowProps } from '.'
import { UpsertRecoveryFlowFields } from '.'
import type { UpsertRecoveryFlowProps } from '.'

export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlowProps }): ReactElement {
export function UpsertRecoveryFlowReview({
params,
moduleAddress,
}: {
params: UpsertRecoveryFlowProps
moduleAddress?: string
}): ReactElement {
const web3 = useWeb3()
const { safe, safeAddress } = useSafeInfo()
const { setSafeTx, safeTxError, setSafeTxError } = useContext(SafeTxContext)

const guardian = params[EnableRecoveryFlowFields.guardians]
const delay = RecoveryDelayPeriods.find(({ value }) => value === params[EnableRecoveryFlowFields.txCooldown])!.label
const expiration = RecoveryExpirationPeriods.find(
({ value }) => value === params[EnableRecoveryFlowFields.txExpiration],
const periods = useRecoveryPeriods()
const guardian = params[UpsertRecoveryFlowFields.guardian]
const delay = periods.delay.find(({ value }) => value === params[UpsertRecoveryFlowFields.txCooldown])!.label
const expiration = periods.expiration.find(
({ value }) => value === params[UpsertRecoveryFlowFields.txExpiration],
)!.label
const emailAddress = params[EnableRecoveryFlowFields.emailAddress]
const emailAddress = params[UpsertRecoveryFlowFields.emailAddress]

useEffect(() => {
if (!web3) {
return
}

const { transactions } = getRecoverySetup({
getRecoveryUpsertTransactions({
...params,
guardians: [guardian],
provider: web3,
chainId: safe.chainId,
safeAddress,
provider: web3,
})
moduleAddress,
}).then((transactions) => {
const promise = transactions.length > 1 ? createMultiSendCallOnlyTx(transactions) : createTx(transactions[0])

createMultiSendCallOnlyTx(transactions).then(setSafeTx).catch(setSafeTxError)
}, [guardian, params, safe.chainId, safeAddress, setSafeTx, setSafeTxError, web3])
promise.then(setSafeTx).catch(setSafeTxError)
})
}, [guardian, moduleAddress, params, safe.chainId, safeAddress, setSafeTx, setSafeTxError, web3])

useEffect(() => {
if (safeTxError) {
Expand All @@ -51,7 +61,9 @@ export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlo

return (
<SignOrExecuteForm onSubmit={() => null}>
<Typography>This transaction will enable the Account recovery feature once executed.</Typography>
<Typography>
This transaction will {moduleAddress ? 'update' : 'enable'} the Account recovery feature once executed.
</Typography>

<TxDataRow title="Trusted guardian">
<EthHashInfo address={guardian} showName={false} hasExplorer showCopyButton avatarSize={24} />
Expand Down
Loading
Loading