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
51 changes: 30 additions & 21 deletions src/components/dashboard/RecoveryHeader/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,65 @@ import { BigNumber } from 'ethers'

import { _RecoveryHeader } from '.'
import { render } from '@/tests/test-utils'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryContext'
import { RecoveryContext } from '@/components/recovery/RecoveryContext'

describe('RecoveryHeader', () => {
it('should not render a widget if the chain does not support recovery', () => {
const queue = [{ validFrom: BigNumber.from(0) }] as any

const { container } = render(
<_RecoveryHeader
isOwner
isGuardian
queue={[{ validFrom: BigNumber.from(0) } as RecoveryQueueItem]}
supportsRecovery={false}
/>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryHeader isOwner isGuardian queue={queue} supportsRecovery={false} />
</RecoveryContext.Provider>,
)

expect(container).toBeEmptyDOMElement()
})

it('should render the in-progress widget if there is a queue for guardians', () => {
const queue = [{ validFrom: BigNumber.from(0) }] as any

const { queryByText } = render(
<_RecoveryHeader
isOwner={false}
isGuardian
queue={[{ validFrom: BigNumber.from(0) } as RecoveryQueueItem]}
supportsRecovery
/>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryHeader isOwner={false} isGuardian queue={queue} supportsRecovery />
</RecoveryContext.Provider>,
)

expect(queryByText('Account recovery in progress')).toBeTruthy()
})

it('should render the in-progress widget if there is a queue for owners', () => {
const queue = [{ validFrom: BigNumber.from(0) }] as any

const { queryByText } = render(
<_RecoveryHeader
isOwner
isGuardian={false}
queue={[{ validFrom: BigNumber.from(0) } as RecoveryQueueItem]}
supportsRecovery
/>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryHeader isOwner isGuardian={false} queue={queue} supportsRecovery />
</RecoveryContext.Provider>,
)

expect(queryByText('Account recovery in progress')).toBeTruthy()
})

it('should render the proposal widget when there is no queue for guardians', () => {
const { queryByText } = render(<_RecoveryHeader isOwner={false} isGuardian queue={[]} supportsRecovery />)
const queue = [] as any

const { queryByText } = render(
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryHeader isOwner={false} isGuardian queue={queue} supportsRecovery />
</RecoveryContext.Provider>,
)

expect(queryByText('Recover this Account')).toBeTruthy()
})

it('should not render the proposal widget when there is no queue for owners', () => {
const { container } = render(<_RecoveryHeader isOwner isGuardian={false} queue={[]} supportsRecovery />)
const queue = [] as any

const { container } = render(
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryHeader isOwner isGuardian={false} queue={queue} supportsRecovery />
</RecoveryContext.Provider>,
)

expect(container).toBeEmptyDOMElement()
})
Expand Down
65 changes: 41 additions & 24 deletions src/components/recovery/RecoveryModal/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { safeInfoBuilder } from '@/tests/builders/safe'
import { connectedWalletBuilder } from '@/tests/builders/wallet'
import * as safeInfo from '@/hooks/useSafeInfo'
import { _useDidDismissProposal } from './index'
import { RecoveryContext } from '@/components/recovery/RecoveryContext'
import type { RecoveryQueueItem } from '@/components/recovery/RecoveryContext'

describe('RecoveryModal', () => {
Expand All @@ -27,9 +28,11 @@ describe('RecoveryModal', () => {
const queue = [{ validFrom: BigNumber.from(0) } as RecoveryQueueItem]

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian={false} queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian={false} queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -41,9 +44,11 @@ describe('RecoveryModal', () => {
const queue = [] as Array<RecoveryQueueItem>

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -55,9 +60,11 @@ describe('RecoveryModal', () => {
const queue = [] as Array<RecoveryQueueItem>

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -69,9 +76,11 @@ describe('RecoveryModal', () => {
const queue = [{ validFrom: BigNumber.from(0) } as RecoveryQueueItem]

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -83,9 +92,11 @@ describe('RecoveryModal', () => {
const queue = [{ validFrom: BigNumber.from(0) } as RecoveryQueueItem]

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -97,9 +108,11 @@ describe('RecoveryModal', () => {
const queue = [] as Array<RecoveryQueueItem>

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner={false} isGuardian queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -111,9 +124,11 @@ describe('RecoveryModal', () => {
const queue = [] as Array<RecoveryQueueItem>

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
Expand All @@ -136,9 +151,11 @@ describe('RecoveryModal', () => {
const queue = [{ validFrom: BigNumber.from(0), transactionHash: faker.string.hexadecimal() } as RecoveryQueueItem]

const { queryByText } = render(
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>,
<RecoveryContext.Provider value={{ state: [[{ queue }]] } as any}>
<_RecoveryModal wallet={wallet} isOwner isGuardian={false} queue={queue}>
Test
</_RecoveryModal>
</RecoveryContext.Provider>,
)

expect(queryByText('Test')).toBeTruthy()
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 @@ -169,7 +169,7 @@ const TxLayout = ({
<Box className={css.sticky}>
<SecurityWarnings />

{isRecovery && <EnableRecoveryFlowEmailHint />}
{isRecovery && <UpsertRecoveryFlowEmailHint />}
</Box>
</Grid>
</Grid>
Expand Down
Loading
Loading