Skip to content

Commit

Permalink
feat: settings + review step
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Nov 13, 2023
1 parent 5d9ac7a commit e89e0f0
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 17 deletions.
9 changes: 8 additions & 1 deletion src/components/settings/Recovery/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import type { ReactElement } from 'react'

import { EnableRecoveryFlow } from '@/components/tx-flow/flows/EnableRecovery'
import { TxModalContext } from '@/components/tx-flow'
import { useDarkMode } from '@/hooks/useDarkMode'

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

return (
<Paper sx={{ p: 4 }}>
Expand All @@ -18,7 +20,12 @@ export function Recovery(): ReactElement {
</Typography>

{/* TODO: Extract when widget is merged https://github.com/safe-global/safe-wallet-web/pull/2768 */}
<Chip label="New" color="primary" size="small" sx={{ borderRadius: '4px', fontSize: '12px' }} />
<Chip
label="New"
color={isDarkMode ? 'primary' : 'secondary'}
size="small"
sx={{ borderRadius: '4px', fontSize: '12px' }}
/>
</Box>
</Grid>

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
5 changes: 5 additions & 0 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 @@ -59,6 +60,7 @@ type TxLayoutProps = {
isBatch?: boolean
isReplacement?: boolean
isMessage?: boolean
isRecovery?: boolean
}

const TxLayout = ({
Expand All @@ -74,6 +76,7 @@ const TxLayout = ({
isBatch = false,
isReplacement = false,
isMessage = false,
isRecovery = false,
}: TxLayoutProps): ReactElement => {
const [statusVisible, setStatusVisible] = useState<boolean>(true)

Expand Down Expand Up @@ -154,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 'src/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
Expand Up @@ -8,6 +8,11 @@ import { SafeTxContext } from '@/components/tx-flow/SafeTxProvider'
import { getRecoverySetup } 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 { 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 '.'

export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlowProps }): ReactElement {
Expand Down Expand Up @@ -39,5 +44,71 @@ export function EnableRecoveryFlowReview({ params }: { params: EnableRecoveryFlo
}
}, [safeTxError])

return <SignOrExecuteForm onSubmit={() => null} />
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 (
<SignOrExecuteForm onSubmit={() => null}>
<Typography>This transaction will enable the Account recovery feature once executed.</Typography>

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

{/* TODO: Info */}
<TxDataRow
title={
<>
Recovery delay
<Tooltip
placement="top"
title="You can cancel any recovery attempt when it is not needed or wanted within the delay period."
>
<span>
<SvgIcon
component={InfoIcon}
inheritViewBox
fontSize="small"
color="border"
sx={{ verticalAlign: 'middle', ml: 0.5 }}
/>
</span>
</Tooltip>
</>
}
>
{delay}
</TxDataRow>
{/* TODO: Info */}
<TxDataRow
title={
<>
Transaction validity
<Tooltip
placement="top"
title="A period after which the recovery attempt will expire and can no longer be executed."
>
<span>
<SvgIcon
component={InfoIcon}
inheritViewBox
fontSize="small"
color="border"
sx={{ verticalAlign: 'middle', ml: 0.5 }}
/>
</span>
</Tooltip>
</>
}
>
{expiration}
</TxDataRow>

{emailAddress ? <TxDataRow title="Email address">{emailAddress}</TxDataRow> : null}
</SignOrExecuteForm>
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
import { Divider, CardActions, Button, Typography } from '@mui/material'
import { useForm, FormProvider } from 'react-hook-form'
import {
Divider,
CardActions,
Button,
Typography,
SvgIcon,
MenuItem,
TextField,
Collapse,
Checkbox,
FormControlLabel,
} from '@mui/material'
import ExpandLessIcon from '@mui/icons-material/ExpandLess'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { useForm, FormProvider, Controller } from 'react-hook-form'
import { useState } from 'react'
import type { TextFieldProps } from '@mui/material'
import type { ReactElement } from 'react'

import TxCard from '../../common/TxCard'
import { EnableRecoveryFlowFields, RecoveryDelayPeriods, RecoveryExpirationPeriods } from '.'
import AddressBookInput from '@/components/common/AddressBookInput'
import CircleCheckIcon from '@/public/images/common/circle-check.svg'
import { useDarkMode } from '@/hooks/useDarkMode'
import type { EnableRecoveryFlowProps } from '.'

import commonCss from '@/components/tx-flow/common/styles.module.css'
import css from './styles.module.css'

export function EnableRecoveryFlowSettings({
params,
Expand All @@ -14,49 +34,135 @@ export function EnableRecoveryFlowSettings({
params: EnableRecoveryFlowProps
onSubmit: (formData: EnableRecoveryFlowProps) => void
}): ReactElement {
const [showAdvanced, setShowAdvanced] = useState(params[EnableRecoveryFlowFields.txExpiration] !== '0')
const [understandsRisk, setUnderstandsRisk] = useState(false)
const isDarkMode = useDarkMode()

const formMethods = useForm<EnableRecoveryFlowProps>({
defaultValues: params,
mode: 'onChange',
})

const emailAddress = formMethods.watch(EnableRecoveryFlowFields.emailAddress)

const onShowAdvanced = () => setShowAdvanced((prev) => !prev)

return (
<>
<FormProvider {...formMethods}>
<form onSubmit={formMethods.handleSubmit(onSubmit)} className={commonCss.form}>
<TxCard>
<Typography variant="h5">Trusted guardian</Typography>

<Typography variant="body2">
Choosen a guardian, such as a hardware wallet or family member&apos;s wallet, that can initiate the
Choose a guardian, such as a hardware wallet or family member&apos;s wallet, that can initiate the
recovery process in the future.
</Typography>

{/* TODO: Address field */}
<AddressBookInput
label="Guardian address"
name={`${EnableRecoveryFlowFields.guardians}.0`}
required
fullWidth
/>

{/* TODO: Info button */}
<Typography variant="h5">Recovery delay</Typography>

<Typography variant="body2">
You can cancel any recovery attempt when it is not needed or wanted within the delay period.
</Typography>

{/* TODO: Delay field */}
<Controller
control={formMethods.control}
name={EnableRecoveryFlowFields.txCooldown}
render={({ field }) => (
<SelectField label="Recovery delay" fullWidth {...field}>
{RecoveryDelayPeriods.map(({ label, value }, index) => (
<MenuItem key={index} value={value}>
{label}
</MenuItem>
))}
</SelectField>
)}
/>

<Typography variant="body2" onClick={onShowAdvanced} role="button" className={css.advanced}>
Advanced {showAdvanced ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</Typography>

<Collapse in={showAdvanced}>
<Typography variant="body2" mb={2}>
Set a period of time after which the recovery attempt will expire and can no longer be executed.
</Typography>

{/* TODO: Advanced options */}
<Controller
control={formMethods.control}
name={EnableRecoveryFlowFields.txExpiration}
// Don't reset value if advanced section is collapsed
shouldUnregister={false}
render={({ field }) => (
<SelectField label="Transaction expiry" fullWidth {...field}>
{RecoveryExpirationPeriods.map(({ label, value }, index) => (
<MenuItem key={index} value={value}>
{label}
</MenuItem>
))}
</SelectField>
)}
/>
</Collapse>
</TxCard>

<TxCard>
{/* TODO: Recommended badge */}
<div className={css.recommended}>
<SvgIcon component={CircleCheckIcon} inheritViewBox color="info" fontSize="small" sx={{ mr: 0.5 }} />
<Typography variant="caption">Recommended</Typography>
</div>

<Typography variant="h5">Receive email updates</Typography>
<Typography variant="body2">Get notified about any recovery initiations and their statuses.</Typography>

{/* TODO: Email address field */}
<Typography variant="body2" mb={1}>
Get notified about any recovery initiations and their statuses.
</Typography>

<Controller
control={formMethods.control}
name={EnableRecoveryFlowFields.emailAddress}
render={({ field }) => (
<TextField
label="Enter email address"
fullWidth
{...field}
InputLabelProps={{
shrink: true,
}}
/>
)}
/>

{/* TODO: Tenderly logo */}
{emailAddress ? (
<div className={css.poweredBy}>
<Typography variant="caption">Powered by </Typography>
<img
src={
isDarkMode ? '/images/transactions/tenderly-light.svg' : '/images/transactions/tenderly-dark.svg'
}
alt="Tenderly"
className={css.tenderly}
/>
</div>
) : (
<FormControlLabel
label="I understand the risks of proceediung without an email address"
control={<Checkbox checked={understandsRisk} onChange={(_, checked) => setUnderstandsRisk(checked)} />}
sx={{ pl: 2 }}
/>
)}

<Divider className={commonCss.nestedDivider} />

<CardActions sx={{ mt: '0 !important' }}>
<Button variant="contained" type="submit">
<Button variant="contained" type="submit" disabled={!emailAddress && !understandsRisk}>
Next
</Button>
</CardActions>
Expand All @@ -66,3 +172,26 @@ export function EnableRecoveryFlowSettings({
</>
)
}

function SelectField(props: TextFieldProps) {
return (
<TextField
{...props}
select
sx={{
'& .MuiSelect-select': {
textAlign: 'right',
fontWeight: 700,
fontSize: '14px',
},
}}
InputLabelProps={{
shrink: false,
sx: {
color: 'text.primary',
fontSize: '14px',
},
}}
/>
)
}
Loading

0 comments on commit e89e0f0

Please sign in to comment.