From 0fb37869516077a163a979e0269b1eecacddd993 Mon Sep 17 00:00:00 2001 From: iamacook Date: Sat, 18 Nov 2023 20:36:33 +0300 Subject: [PATCH 1/5] fix: cleanup code + rename test --- .../EnableRecoveryFlowReview.tsx | 29 +++++++++---------- .../EnableRecoveryFlowSettings.tsx | 7 +---- .../tx-flow/flows/EnableRecovery/index.tsx | 19 +++++------- src/services/recovery/__tests__/setup.test.ts | 2 +- 4 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx index 97bb575dcb..245087b784 100644 --- a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx +++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowReview.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect, useMemo } from 'react' +import { useContext, useEffect } from 'react' import type { ReactElement } from 'react' import SignOrExecuteForm from '@/components/tx/SignOrExecuteForm' @@ -20,23 +20,27 @@ export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlo const { safe } = useSafeInfo() const { setSafeTx, safeTxError, setSafeTxError } = useContext(SafeTxContext) - const recoverySetup = useMemo(() => { + const guardian = params[EnableRecoveryFlowFields.guardians] + const delay = RecoveryDelayPeriods.find(({ value }) => value === params[EnableRecoveryFlowFields.txCooldown])!.label + const expiration = RecoveryExpirationPeriods.find( + ({ value }) => value === params[EnableRecoveryFlowFields.txExpiration], + )!.label + const emailAddress = params[EnableRecoveryFlowFields.emailAddress] + + useEffect(() => { if (!web3) { return } - return getRecoverySetup({ + const { transactions } = getRecoverySetup({ ...params, + guardians: [guardian], safe, provider: web3, }) - }, [params, safe, web3]) - useEffect(() => { - if (recoverySetup) { - createMultiSendCallOnlyTx(recoverySetup.transactions).then(setSafeTx).catch(setSafeTxError) - } - }, [recoverySetup, setSafeTx, setSafeTxError]) + createMultiSendCallOnlyTx(transactions).then(setSafeTx).catch(setSafeTxError) + }, [guardian, params, safe, setSafeTx, setSafeTxError, web3]) useEffect(() => { if (safeTxError) { @@ -44,13 +48,6 @@ export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlo } }, [safeTxError]) - const guardian = params[EnableRecoveryFlowFields.guardians][0] - const delay = RecoveryDelayPeriods.find(({ value }) => value === params[EnableRecoveryFlowFields.txCooldown])!.label - const expiration = RecoveryExpirationPeriods.find( - ({ value }) => value === params[EnableRecoveryFlowFields.txExpiration], - )!.label - const emailAddress = params[EnableRecoveryFlowFields.emailAddress] - return ( null}> This transaction will enable the Account recovery feature once executed. diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx index a404ed6e73..5cd84668c9 100644 --- a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx +++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx @@ -59,12 +59,7 @@ export function EnableRecoveryFlowSettings({ recovery process in the future. - + Recovery delay diff --git a/src/components/tx-flow/flows/EnableRecovery/index.tsx b/src/components/tx-flow/flows/EnableRecovery/index.tsx index 376d70bead..18a5ea88ef 100644 --- a/src/components/tx-flow/flows/EnableRecovery/index.tsx +++ b/src/components/tx-flow/flows/EnableRecovery/index.tsx @@ -40,6 +40,8 @@ export const RecoveryExpirationPeriods = [ ...RecoveryDelayPeriods, ] as const +const Subtitles = ['How does recovery work?', 'Set up recovery settings', 'Set up account recovery'] + export enum EnableRecoveryFlowFields { guardians = 'guardians', txCooldown = 'txCooldown', @@ -48,7 +50,7 @@ export enum EnableRecoveryFlowFields { } export type EnableRecoveryFlowProps = { - [EnableRecoveryFlowFields.guardians]: Array + [EnableRecoveryFlowFields.guardians]: string [EnableRecoveryFlowFields.txCooldown]: string [EnableRecoveryFlowFields.txExpiration]: string [EnableRecoveryFlowFields.emailAddress]: string @@ -56,8 +58,8 @@ export type EnableRecoveryFlowProps = { export function EnableRecoveryFlow(): ReactElement { const { data, step, nextStep, prevStep } = useTxStepper({ - [EnableRecoveryFlowFields.guardians]: [''], - [EnableRecoveryFlowFields.txCooldown]: `${60 * 60 * 24 * 28}`, // 28 days in seconds + [EnableRecoveryFlowFields.guardians]: '', + [EnableRecoveryFlowFields.txCooldown]: `${DAY_SECONDS * 28}`, // 28 days in seconds [EnableRecoveryFlowFields.txExpiration]: '0', [EnableRecoveryFlowFields.emailAddress]: '', }) @@ -65,24 +67,17 @@ export function EnableRecoveryFlow(): ReactElement { const steps = [ nextStep(data)} />, nextStep({ ...data, ...formData })} />, - , + , ] const isIntro = step === 0 - const isSettings = step === 1 - - const subtitle = isIntro - ? 'How does recovery work?' - : isSettings - ? 'Set up account recovery settings' - : 'Set up account recovery' const icon = isIntro ? undefined : RecoveryPlus return ( { jest.clearAllMocks() }) - it('should deploy Delay Modifier, enable it on Safe and add a Guardian', () => { + it('should return deploy Delay Modifier, enable Safe guardian and add a Guardian transactions', () => { const txCooldown = faker.string.numeric() const txExpiration = faker.string.numeric() const guardians = [faker.finance.ethereumAddress()] From 54f2aeaaabcbce818b15efdabc4709c49059c8be Mon Sep 17 00:00:00 2001 From: iamacook Date: Mon, 20 Nov 2023 09:29:55 +0100 Subject: [PATCH 2/5] fix: test --- src/hooks/__tests__/useLoadRecovery.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hooks/__tests__/useLoadRecovery.test.ts b/src/hooks/__tests__/useLoadRecovery.test.ts index fb77e655c7..f4b693b873 100644 --- a/src/hooks/__tests__/useLoadRecovery.test.ts +++ b/src/hooks/__tests__/useLoadRecovery.test.ts @@ -127,6 +127,12 @@ describe('useLoadRecovery', () => { txNonce, queueNonce, queue: [ + { + ...transactionsAdded[0], + timestamp: 69, + validFrom: BigNumber.from(69).add(txCooldown), + expiresAt: null, + }, { ...transactionsAdded[1], timestamp: 420, From 2a78391b6029fb5b7ddaa5c84656125e7f7f9333 Mon Sep 17 00:00:00 2001 From: iamacook Date: Mon, 20 Nov 2023 10:24:41 +0100 Subject: [PATCH 3/5] fix: spacing + add connector --- src/components/settings/Recovery/index.tsx | 14 +++++++--- .../EnableRecoveryFlowIntro.tsx | 19 ++++++++------ .../EnableRecoveryFlowSettings.tsx | 26 ++++++++++++------- .../flows/EnableRecovery/styles.module.css | 16 ++++++++++++ 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/components/settings/Recovery/index.tsx b/src/components/settings/Recovery/index.tsx index 3783806a2c..d5a1704e37 100644 --- a/src/components/settings/Recovery/index.tsx +++ b/src/components/settings/Recovery/index.tsx @@ -1,10 +1,11 @@ -import { Box, Button, Chip, Grid, Paper, Typography } from '@mui/material' +import { Alert, Box, Button, Chip, Grid, Paper, Typography } from '@mui/material' import { useContext } from 'react' import type { ReactElement } from 'react' import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery' import { TxModalContext } from '@/components/tx-flow' import { useDarkMode } from '@/hooks/useDarkMode' +import ExternalLink from '@/components/common/ExternalLink' export function Recovery(): ReactElement { const { setTxFlow } = useContext(TxModalContext) @@ -30,12 +31,19 @@ export function Recovery(): ReactElement { - + Choose a trusted guardian to recover your Safe Account, in case you should ever lose access to your Account. Enabling the Account recovery module will require a transactions. - diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx index 6e75d7aa85..bd727faf11 100644 --- a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx +++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowIntro.tsx @@ -7,22 +7,23 @@ import RecoveryGuardian from '@/public/images/transactions/recovery-guardian.svg import RecoveryDelay from '@/public/images/settings/spending-limit/time.svg' import RecoveryExecution from '@/public/images/transactions/recovery-execution.svg' +import css from './styles.module.css' import commonCss from '@/components/tx-flow/common/styles.module.css' -const RecoverySteps: Array<{ icon: ReactElement; title: string; subtitle: ReactNode }> = [ +const RecoverySteps: Array<{ Icon: ReactElement; title: string; subtitle: ReactNode }> = [ { - icon: , + Icon: RecoveryGuardians, title: 'Choose a guardian and set a delay', subtitle: 'Only your chosen guardian can initiate the recovery process. The process can be cancelled at any time during the delay period.', }, { - icon: , + Icon: RecoveryGuardian, title: 'Lost access? Let the guardian connect', subtitle: 'The recovery process can be initiated by a trusted guardian when connected to your Safe Account.', }, { - icon: , + Icon: RecoveryDelay, title: 'Start the recovery process', subtitle: ( <> @@ -32,7 +33,7 @@ const RecoverySteps: Array<{ icon: ReactElement; title: string; subtitle: ReactN ), }, { - icon: , + Icon: RecoveryExecution, title: 'All done! The Account is yours again', subtitle: 'Once the delay period has passed, you can execute the recovery transaction and regain access to your Safe Account.', @@ -42,11 +43,13 @@ const RecoverySteps: Array<{ icon: ReactElement; title: string; subtitle: ReactN export function EnableRecoveryFlowIntro({ onSubmit }: { onSubmit: () => void }): ReactElement { return ( - - {RecoverySteps.map(({ icon, title, subtitle }, index) => ( + + {RecoverySteps.map(({ Icon, title, subtitle }, index) => ( - {icon} + + + {title} diff --git a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx index 5cd84668c9..7bbac1363f 100644 --- a/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx +++ b/src/components/tx-flow/flows/EnableRecovery/EnableRecoveryFlowSettings.tsx @@ -52,20 +52,28 @@ export function EnableRecoveryFlowSettings({
- Trusted guardian +
+ + Trusted guardian + - - Choose a guardian, such as a hardware wallet or family member's wallet, that can initiate the - recovery process in the future. - + + Choose a guardian, such as a hardware wallet or family member's wallet, that can initiate the + recovery process in the future. + +
- Recovery delay +
+ + Recovery delay + - - You can cancel any recovery attempt when it is not needed or wanted within the delay period. - + + You can cancel any recovery attempt when it is not needed or wanted within the delay period. + +
Date: Mon, 20 Nov 2023 10:33:43 +0100 Subject: [PATCH 4/5] feat: recovery dashboard widget (#2768) * feat: recovery dashboard widget * fix: remove relaying widget * fix: `tx-builder.spec` test * fix: badge colour * fix: test --- public/images/common/recovery.svg | 11 +++ src/components/dashboard/Recovery/index.tsx | 48 +++++++++++ .../dashboard/Recovery/styles.module.css | 32 ++++++++ src/components/dashboard/Relaying/index.tsx | 80 ------------------- .../dashboard/Relaying/styles.module.css | 33 -------- src/components/dashboard/index.tsx | 12 +-- src/hooks/__tests__/useLoadRecovery.test.ts | 6 ++ 7 files changed, 103 insertions(+), 119 deletions(-) create mode 100644 public/images/common/recovery.svg create mode 100644 src/components/dashboard/Recovery/index.tsx create mode 100644 src/components/dashboard/Recovery/styles.module.css delete mode 100644 src/components/dashboard/Relaying/index.tsx delete mode 100644 src/components/dashboard/Relaying/styles.module.css diff --git a/public/images/common/recovery.svg b/public/images/common/recovery.svg new file mode 100644 index 0000000000..70e4d13a9e --- /dev/null +++ b/public/images/common/recovery.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/dashboard/Recovery/index.tsx b/src/components/dashboard/Recovery/index.tsx new file mode 100644 index 0000000000..ec5e5faa8d --- /dev/null +++ b/src/components/dashboard/Recovery/index.tsx @@ -0,0 +1,48 @@ +import { Box, Button, Card, Chip, Grid, Typography } from '@mui/material' +import type { ReactElement } from 'react' + +import RecoveryLogo from '@/public/images/common/recovery.svg' +import { WidgetBody, WidgetContainer } from '@/components/dashboard/styled' +import { useDarkMode } from '@/hooks/useDarkMode' + +import css from './styles.module.css' + +export function Recovery(): ReactElement { + const isDarkMode = useDarkMode() + + const onClick = () => { + // TODO: Open enable recovery flow + } + + return ( + + + New in {'Safe{Wallet}'} + + + + + + + + + + + + Introducing account recovery{' '} + + + + + Ensure that you never lose access to your funds by choosing a guardian to recover your account. + + + + + + + + ) +} diff --git a/src/components/dashboard/Recovery/styles.module.css b/src/components/dashboard/Recovery/styles.module.css new file mode 100644 index 0000000000..df21b43216 --- /dev/null +++ b/src/components/dashboard/Recovery/styles.module.css @@ -0,0 +1,32 @@ +.label { + font-weight: 700; + margin-bottom: var(--space-2); +} + +.card { + padding: var(--space-4); + height: inherit; +} + +.grid { + display: flex; + align-items: center; + height: inherit; + gap: var(--space-3); +} + +.wrapper { + display: flex; + align-items: center; + gap: var(--space-1); +} + +.title { + font-weight: 700; + display: inline; +} + +.chip { + border-radius: 4px; + font-size: 12px; +} diff --git a/src/components/dashboard/Relaying/index.tsx b/src/components/dashboard/Relaying/index.tsx deleted file mode 100644 index 345b42b651..0000000000 --- a/src/components/dashboard/Relaying/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { WidgetBody, WidgetContainer } from '@/components/dashboard/styled' -import { Box, Card, Divider, Skeleton, Stack, SvgIcon, Typography } from '@mui/material' -import { MAX_HOUR_RELAYS, useRelaysBySafe } from '@/hooks/useRemainingRelays' -import { OVERVIEW_EVENTS } from '@/services/analytics' -import Track from '@/components/common/Track' -import InfoIcon from '@/public/images/notifications/info.svg' -import GasStationIcon from '@/public/images/common/gas-station.svg' -import ExternalLink from '@/components/common/ExternalLink' -import classnames from 'classnames' -import css from './styles.module.css' -import { HelpCenterArticle } from '@/config/constants' -import { useCurrentChain } from '@/hooks/useChains' -import { SPONSOR_LOGOS } from '@/components/tx/SponsoredBy' - -const Relaying = () => { - const chain = useCurrentChain() - const [relays, relaysError] = useRelaysBySafe() - - const limit = relays?.limit || MAX_HOUR_RELAYS - - return ( - - - New in {'Safe{Wallet}'} - - - - - - - - - - - Gas fees sponsored by - - {chain?.chainName} - - {chain?.chainName} - - - - Benefit from a gasless experience powered by Gelato and Safe. Experience gasless UX for the next - month! - - - Read more - - - - - - Transactions per hour - - {relays !== undefined ? ( - - - {relaysError ? ( - {limit} per hour - ) : ( - - {relays.remaining} of {limit} - - )} - - ) : ( - - )} - - - - - ) -} - -export default Relaying diff --git a/src/components/dashboard/Relaying/styles.module.css b/src/components/dashboard/Relaying/styles.module.css deleted file mode 100644 index cc02616d25..0000000000 --- a/src/components/dashboard/Relaying/styles.module.css +++ /dev/null @@ -1,33 +0,0 @@ -.relayingChip { - padding: var(--space-1); - border-radius: 6px; - height: 30px; - background-color: var(--color-secondary-light); - color: var(--color-static-main); - display: flex; - align-items: center; - gap: var(--space-1); -} - -.relayingChip.unavailable { - background-color: var(--color-error-light); -} - -.chipSkeleton { - height: 30px; - width: 80px; -} - -.icon { - padding: 4px; - border-radius: 6px; - border: 1.3px solid var(--color-text-primary); - width: 34px; - height: 34px; - margin-right: 12px; -} - -.gcLogo { - width: 16px; - height: 16px; -} diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index 497aed2b7e..6d30bb0bac 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -7,14 +7,14 @@ import SafeAppsDashboardSection from '@/components/dashboard/SafeAppsDashboardSe import GovernanceSection from '@/components/dashboard/GovernanceSection/GovernanceSection' import CreationDialog from '@/components/dashboard/CreationDialog' import { useRouter } from 'next/router' -import Relaying from '@/components/dashboard/Relaying' +import { Recovery } from './Recovery' import { FEATURES } from '@/utils/chains' import { useHasFeature } from '@/hooks/useChains' import { CREATION_MODAL_QUERY_PARM } from '../new-safe/create/logic' const Dashboard = (): ReactElement => { const router = useRouter() - const supportsRelaying = useHasFeature(FEATURES.RELAYING) + const supportsRecovery = useHasFeature(FEATURES.RECOVERY) const { [CREATION_MODAL_QUERY_PARM]: showCreationModal = '' } = router.query return ( @@ -28,13 +28,13 @@ const Dashboard = (): ReactElement => { - - + + - {supportsRelaying ? ( + {supportsRecovery ? ( - + ) : null} diff --git a/src/hooks/__tests__/useLoadRecovery.test.ts b/src/hooks/__tests__/useLoadRecovery.test.ts index fb77e655c7..f4b693b873 100644 --- a/src/hooks/__tests__/useLoadRecovery.test.ts +++ b/src/hooks/__tests__/useLoadRecovery.test.ts @@ -127,6 +127,12 @@ describe('useLoadRecovery', () => { txNonce, queueNonce, queue: [ + { + ...transactionsAdded[0], + timestamp: 69, + validFrom: BigNumber.from(69).add(txCooldown), + expiresAt: null, + }, { ...transactionsAdded[1], timestamp: 420, From 5acef7a2024afc67a519074664127626709fdfe7 Mon Sep 17 00:00:00 2001 From: iamacook Date: Mon, 20 Nov 2023 10:43:11 +0100 Subject: [PATCH 5/5] refactor: extract `Chip` component --- src/components/common/Chip/index.tsx | 18 ++++++++++++++++++ src/components/dashboard/Recovery/index.tsx | 13 ++++++++----- .../dashboard/Recovery/styles.module.css | 5 ----- src/components/settings/Recovery/index.tsx | 13 +++---------- 4 files changed, 29 insertions(+), 20 deletions(-) create mode 100644 src/components/common/Chip/index.tsx diff --git a/src/components/common/Chip/index.tsx b/src/components/common/Chip/index.tsx new file mode 100644 index 0000000000..56fb9c3137 --- /dev/null +++ b/src/components/common/Chip/index.tsx @@ -0,0 +1,18 @@ +import { Chip as MuiChip } from '@mui/material' +import type { ChipProps } from '@mui/material' +import type { ReactElement } from 'react' + +import { useDarkMode } from '@/hooks/useDarkMode' + +export function Chip(props: ChipProps): ReactElement { + const isDarkMode = useDarkMode() + return ( + + ) +} diff --git a/src/components/dashboard/Recovery/index.tsx b/src/components/dashboard/Recovery/index.tsx index ec5e5faa8d..0227367f34 100644 --- a/src/components/dashboard/Recovery/index.tsx +++ b/src/components/dashboard/Recovery/index.tsx @@ -1,17 +1,20 @@ -import { Box, Button, Card, Chip, Grid, Typography } from '@mui/material' +import { Box, Button, Card, Grid, Typography } from '@mui/material' +import { useContext } from 'react' import type { ReactElement } from 'react' import RecoveryLogo from '@/public/images/common/recovery.svg' import { WidgetBody, WidgetContainer } from '@/components/dashboard/styled' -import { useDarkMode } from '@/hooks/useDarkMode' +import { Chip } from '@/components/common/Chip' +import { TxModalContext } from '@/components/tx-flow' +import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery' import css from './styles.module.css' export function Recovery(): ReactElement { - const isDarkMode = useDarkMode() + const { setTxFlow } = useContext(TxModalContext) const onClick = () => { - // TODO: Open enable recovery flow + setTxFlow() } return ( @@ -31,7 +34,7 @@ export function Recovery(): ReactElement { Introducing account recovery{' '} - + Ensure that you never lose access to your funds by choosing a guardian to recover your account. diff --git a/src/components/dashboard/Recovery/styles.module.css b/src/components/dashboard/Recovery/styles.module.css index df21b43216..352f6e4e6b 100644 --- a/src/components/dashboard/Recovery/styles.module.css +++ b/src/components/dashboard/Recovery/styles.module.css @@ -25,8 +25,3 @@ font-weight: 700; display: inline; } - -.chip { - border-radius: 4px; - font-size: 12px; -} diff --git a/src/components/settings/Recovery/index.tsx b/src/components/settings/Recovery/index.tsx index d5a1704e37..fd355b49b7 100644 --- a/src/components/settings/Recovery/index.tsx +++ b/src/components/settings/Recovery/index.tsx @@ -1,15 +1,14 @@ -import { Alert, Box, Button, Chip, Grid, Paper, Typography } from '@mui/material' +import { Alert, Box, Button, Grid, Paper, Typography } from '@mui/material' import { useContext } from 'react' import type { ReactElement } from 'react' import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery' import { TxModalContext } from '@/components/tx-flow' -import { useDarkMode } from '@/hooks/useDarkMode' +import { Chip } from '@/components/common/Chip' import ExternalLink from '@/components/common/ExternalLink' export function Recovery(): ReactElement { const { setTxFlow } = useContext(TxModalContext) - const isDarkMode = useDarkMode() return ( @@ -20,13 +19,7 @@ export function Recovery(): ReactElement { Account recovery - {/* TODO: Extract when widget is merged https://github.com/safe-global/safe-wallet-web/pull/2768 */} - +