Skip to content

Commit

Permalink
feat: enable recovery flow structure (#2775)
Browse files Browse the repository at this point in the history
* feat: enable recovery flow structure

* feat: intro step

* feat: basic settings template

* feat: settings + review step

* fix: add test coverage + remove comments

* fix: cleanup code + rename test

* fix: test

* fix: spacing + add connector

* refactor: extract `Chip` component

* fix: rerender + spacing
  • Loading branch information
iamacook authored Nov 20, 2023
1 parent ce2702a commit 41841cc
Show file tree
Hide file tree
Showing 20 changed files with 870 additions and 15 deletions.
12 changes: 12 additions & 0 deletions public/images/common/recovery-plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions public/images/transactions/recovery-execution.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/images/transactions/recovery-guardian.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions src/components/common/Chip/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<MuiChip
label="New"
color={isDarkMode ? 'primary' : 'secondary'}
size="small"
sx={{ borderRadius: '4px', fontSize: '12px' }}
{...props}
/>
)
}
13 changes: 8 additions & 5 deletions src/components/dashboard/Recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -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(<EnableRecoveryFlow />)
}

return (
Expand All @@ -31,7 +34,7 @@ export function Recovery(): ReactElement {
<Typography variant="h4" className={css.title}>
Introducing account recovery{' '}
</Typography>
<Chip label="New" color={isDarkMode ? 'primary' : 'secondary'} size="small" className={css.chip} />
<Chip label="New" />
</Box>
<Typography mt={1} mb={3}>
Ensure that you never lose access to your funds by choosing a guardian to recover your account.
Expand Down
5 changes: 0 additions & 5 deletions src/components/dashboard/Recovery/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,3 @@
font-weight: 700;
display: inline;
}

.chip {
border-radius: 4px;
font-size: 12px;
}
46 changes: 46 additions & 0 deletions src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 { Chip } from '@/components/common/Chip'
import ExternalLink from '@/components/common/ExternalLink'

export function Recovery(): ReactElement {
const { setTxFlow } = useContext(TxModalContext)

return (
<Paper sx={{ p: 4 }}>
<Grid container spacing={3}>
<Grid item lg={4} xs={12}>
<Box display="flex" alignItems="center" gap={1} mb={1}>
<Typography variant="h4" fontWeight="bold">
Account recovery
</Typography>

<Chip label="New" />
</Box>
</Grid>

<Grid item xs>
<Typography mb={2}>
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.
</Typography>

<Alert severity="info">
Unhappy with the provided option? {/* TODO: Add link */}
<ExternalLink noIcon href="#">
Give us feedback
</ExternalLink>
</Alert>

<Button variant="contained" onClick={() => setTxFlow(<EnableRecoveryFlow />)} sx={{ mt: 2 }}>
Set up recovery
</Button>
</Grid>
</Grid>
</Paper>
)
}
4 changes: 4 additions & 0 deletions src/components/sidebar/SidebarNavigation/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export const settingsNavItems = [
label: 'Appearance',
href: AppRoutes.settings.appearance,
},
{
label: 'Recovery',
href: AppRoutes.settings.recovery,
},
{
label: 'Notifications',
href: AppRoutes.settings.notifications,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import css from './styles.module.css'
import EthHashInfo from '@/components/common/EthHashInfo'

type TxDataRowProps = {
title: string
title: ReactNode
children?: ReactNode
}

Expand Down
15 changes: 12 additions & 3 deletions src/components/tx-flow/common/TxLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +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'

const TxLayoutHeader = ({
hideNonce,
Expand Down Expand Up @@ -55,9 +56,11 @@ type TxLayoutProps = {
txSummary?: TransactionSummary
onBack?: () => void
hideNonce?: boolean
hideProgress?: boolean
isBatch?: boolean
isReplacement?: boolean
isMessage?: boolean
isRecovery?: boolean
}

const TxLayout = ({
Expand All @@ -69,9 +72,11 @@ const TxLayout = ({
txSummary,
onBack,
hideNonce = false,
hideProgress = false,
isBatch = false,
isReplacement = false,
isMessage = false,
isRecovery = false,
}: TxLayoutProps): ReactElement => {
const [statusVisible, setStatusVisible] = useState<boolean>(true)

Expand Down Expand Up @@ -117,9 +122,11 @@ const TxLayout = ({
</div>

<Paper className={css.header}>
<Box className={css.progressBar}>
<ProgressBar value={progress} />
</Box>
{!hideProgress && (
<Box className={css.progressBar}>
<ProgressBar value={progress} />
</Box>
)}

<TxLayoutHeader subtitle={subtitle} icon={icon} hideNonce={hideNonce} />
</Paper>
Expand Down Expand Up @@ -150,6 +157,8 @@ const TxLayout = ({

<Box className={css.sticky}>
<SecurityWarnings />

{isRecovery && <EnableRecoveryFlowEmailHint />}
</Box>
</Grid>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Box, Typography, SvgIcon, Alert } from '@mui/material'
import type { ReactElement } from 'react'

import LightbulbIcon from '@/public/images/common/lightbulb.svg'

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

export function EnableRecoveryFlowEmailHint(): ReactElement {
return (
<Alert severity="info" sx={{ border: 'unset', p: 3 }} icon={false}>
<Box className={infoWidgetCss.title} sx={{ backgroundColor: ({ palette }) => palette.info.main }}>
<SvgIcon component={LightbulbIcon} inheritViewBox className={infoWidgetCss.titleIcon} />
<Typography variant="caption">
<b>Security tip</b>
</Typography>
</Box>
<Typography variant="body2" mt={2}>
For security reasons, we highly recommend adding an email address. You will be notified once a Guardian
initiates recovery and be able to reject it if it&apos;s a malicious attempt.
</Typography>
</Alert>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Button, CardActions, Divider, Grid, Typography } from '@mui/material'
import type { ReactElement, ReactNode } from 'react'

import TxCard from '../../common/TxCard'
import RecoveryGuardians from '@/public/images/settings/spending-limit/beneficiary.svg'
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 }> = [
{
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: 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: RecoveryDelay,
title: 'Start the recovery process',
subtitle: (
<>
Your <b>guardian</b> chooses new Safe Account owner(s) that you control and can initiates the recovery
transaction.
</>
),
},
{
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.',
},
]

export function EnableRecoveryFlowIntro({ onSubmit }: { onSubmit: () => void }): ReactElement {
return (
<TxCard>
<Grid container display="flex" gap={4} className={css.connector}>
{RecoverySteps.map(({ Icon, title, subtitle }, index) => (
<Grid item xs={12} key={index}>
<Grid container display="flex" gap={3}>
<Grid item>
<Icon className={css.icon} />
</Grid>
<Grid item xs>
<Typography variant="h5" mb={0.5}>
{title}
</Typography>
<Typography variant="body2">{subtitle}</Typography>
</Grid>
</Grid>
</Grid>
))}
</Grid>

<Divider className={commonCss.nestedDivider} />

<CardActions sx={{ mt: 'var(--space-1) !important' }}>
<Button variant="contained" onClick={onSubmit}>
Next
</Button>
</CardActions>
</TxCard>
)
}
Loading

0 comments on commit 41841cc

Please sign in to comment.