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: enable recovery flow structure #2775

Merged
merged 13 commits into from
Nov 20, 2023
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>

usame-algan marked this conversation as resolved.
Show resolved Hide resolved
<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,
},
Comment on lines +87 to +90
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intend merge the settings areas according to the design in a separate PR.

{
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 />}
usame-algan marked this conversation as resolved.
Show resolved Hide resolved
</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 'src/components/new-safe/create/InfoWidget/styles.module.css'
iamacook marked this conversation as resolved.
Show resolved Hide resolved

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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Figma design has a button "Got it" on this hint, I assume to close it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TanyaEfremova, should this button close the widget indefinitely or only in the current flow? We don't allow users to close hints in any other flows (I personally don't think we should include this).

</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
Loading