Skip to content

Commit

Permalink
feat: display legal disclaimer when swapping for the first time (#3623)
Browse files Browse the repository at this point in the history
* Display legal disclaimer to first time swappers

* check for consent within swap component

* fix: remove test address from blocked addresses

* fix: remove test code
  • Loading branch information
jmealy authored and compojoom committed May 2, 2024
1 parent 828fb03 commit ef93ae5
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 84 deletions.
25 changes: 0 additions & 25 deletions src/components/common/BlockedAddress/index.stories.tsx

This file was deleted.

53 changes: 15 additions & 38 deletions src/components/common/BlockedAddress/index.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,28 @@
import type { ReactElement } from 'react'
import { Box, Button, Divider, Paper, Stack, SvgIcon, Typography, useMediaQuery, useTheme } from '@mui/material'
import Link from 'next/link'
import InfoIcon from '@/public/images/notifications/info.svg'
import css from './styles.module.css'
import { useMediaQuery, useTheme } from '@mui/material'
import { shortenAddress } from '@/utils/formatters'
import { useRouter } from 'next/router'
import Disclaimer from '@/components/common/Disclaimer'
import { AppRoutes } from '@/config/routes'

export const BlockedAddress = ({ address }: { address: string }): ReactElement => {
export const BlockedAddress = ({ address }: { address?: string }): ReactElement => {
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
const displayAddress = isMobile ? shortenAddress(address) : address
const displayAddress = address && isMobile ? shortenAddress(address) : address
const router = useRouter()

return (
<div className={css.container}>
<Paper sx={{ maxWidth: '500px' }}>
<Stack
padding="var(--space-3)"
gap={2}
display="flex"
alignItems="center"
sx={({ palette }) => ({ borderBottom: `1px solid ${palette.border.light}` })}
>
<Typography color="var(--color-text-Secondary, #A1A3A7)">{displayAddress}</Typography>
const handleAccept = () => {
router.push({ pathname: AppRoutes.home, query: router.query })
}

<Box className={css.iconCircle}>
<SvgIcon component={InfoIcon} inheritViewBox fontSize="medium" />
</Box>
<Typography variant="h3" fontWeight={700}>
Blocked Address
</Typography>
<Typography variant="body2">
This signer address is blocked by the Safe interface, due to being associated with the blocked activities by
the U.S. Department of Treasury in the Specially Designated Nationals (SDN) list.{' '}
</Typography>
<Divider />
</Stack>
<Box display="flex" justifyContent="center" pt={3} pb={2}>
<Link href={{ pathname: AppRoutes.home, query: router.query }}>
<Button variant="contained" size="small" sx={{ px: '16px' }}>
Got it
</Button>
</Link>
</Box>
</Paper>
</div>
return (
<Disclaimer
title="Blocked Address"
subtitle={displayAddress}
content="This signer address is blocked by the Safe interface, due to being associated with the blocked activities by
the U.S. Department of Treasury in the Specially Designated Nationals (SDN) list."
onAccept={handleAccept}
/>
)
}

Expand Down
39 changes: 39 additions & 0 deletions src/components/common/Disclaimer/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react'
import Disclaimer from './index'
import LegalDisclaimerContent from '@/components/common/LegalDisclaimerContent'

const meta = {
component: Disclaimer,
parameters: {
componentSubtitle: 'Renders a Block for displaying information to the user, with a button to accept.',
},
tags: ['autodocs'],
} satisfies Meta<typeof Disclaimer>

export default meta
type Story = StoryObj<typeof meta>

export const BlockedAddress: Story = {
args: {
subtitle: '0xD3a484faEa53313eF85b5916C9302a3E304ae622',
title: 'Blocked Address',
content:
'This signer address is blocked by the Safe interface, due to being associated with the blocked activities by the U.S. Department of Treasury in the Specially Designated Nationals (SDN) list.',
onAccept: () => {},
},
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/VyA38zUPbJ2zflzCIYR6Nu/Swap?node-id=6167%3A14371&mode=dev',
},
},
}

export const LegalDisclaimer: Story = {
args: {
title: 'Legal Disclaimer',
content: <LegalDisclaimerContent withTitle={false} />,
buttonText: 'Continue',
onAccept: () => {},
},
}
50 changes: 50 additions & 0 deletions src/components/common/Disclaimer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ReactElement, ReactNode } from 'react'
import { Box, Button, Divider, Paper, Stack, SvgIcon, Typography } from '@mui/material'
import InfoIcon from '@/public/images/notifications/info.svg'
import css from './styles.module.css'

export const Disclaimer = ({
title,
subtitle,
buttonText,
content,
onAccept,
}: {
title: string
subtitle?: string
buttonText?: string
content: ReactNode
onAccept: () => void
}): ReactElement => {
return (
<div className={css.container}>
<Paper sx={{ maxWidth: '500px' }}>
<Stack
padding="var(--space-3)"
gap={2}
display="flex"
alignItems="center"
sx={({ palette }) => ({ borderBottom: `1px solid ${palette.border.light}` })}
>
{subtitle && <Typography color="var(--color-text-Secondary, #A1A3A7)">{subtitle}</Typography>}

<Box className={css.iconCircle}>
<SvgIcon component={InfoIcon} inheritViewBox fontSize="medium" />
</Box>
<Typography variant="h3" fontWeight={700}>
{title}
</Typography>
<Typography variant="body2">{content}</Typography>
<Divider />
</Stack>
<Box display="flex" justifyContent="center" pt={3} pb={2}>
<Button variant="contained" size="small" sx={{ px: '16px' }} onClick={onAccept}>
{buttonText || 'Got it'}
</Button>
</Box>
</Paper>
</div>
)
}

export default Disclaimer
14 changes: 14 additions & 0 deletions src/components/common/Disclaimer/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}

.iconCircle {
color: var(--color-info-main);
border-radius: 50%;
display: flex;
padding: var(--space-1);
background: #d7f6ff;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ import { Typography } from '@mui/material'

import css from './styles.module.css'

const LegalDisclaimer = (): JSX.Element => (
const LegalDisclaimerContent = ({ withTitle = true }: { withTitle?: boolean }): JSX.Element => (
<div className={css.disclaimerContainer}>
<Typography variant="body2" color="text.secondary" mx={8}>
Before starting to use Safe dApps...
</Typography>
<Typography variant="h3" fontWeight={700} my={3}>
Disclaimer
</Typography>
{withTitle && (
<Typography variant="h3" fontWeight={700} my={3}>
Disclaimer
</Typography>
)}
<div className={css.disclaimerInner}>
<Typography mb={4}>
You are now accessing third-party apps, which we do not own, control, maintain or audit. We are not liable for
any loss you may suffer in connection with interacting with the apps, which is at your own risk.
You are now accessing a third-party app, which we do not own, control, maintain or audit. We are not liable for
any loss you may suffer in connection with interacting with the app, which is at your own risk.
</Typography>

<Typography mb={4}>
Expand All @@ -33,4 +32,4 @@ const LegalDisclaimer = (): JSX.Element => (
</div>
)

export default LegalDisclaimer
export default LegalDisclaimerContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.disclaimerContainer p,
.disclaimerContainer h3 {
line-height: 24px;
}

.disclaimerInner p {
text-align: justify;
}
4 changes: 2 additions & 2 deletions src/components/safe-apps/SafeAppsInfoModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { Grid, LinearProgress } from '@mui/material'

import type { BrowserPermission } from '@/hooks/safe-apps/permissions'
import Slider from './Slider'
import LegalDisclaimer from './LegalDisclaimer'
import AllowedFeaturesList from './AllowedFeaturesList'
import type { AllowedFeatures, AllowedFeatureSelection } from '../types'
import { PermissionStatus } from '../types'
import UnknownAppWarning from './UnknownAppWarning'
import { getOrigin } from '../utils'
import LegalDisclaimerContent from '@/components/common/LegalDisclaimerContent'

type SafeAppsInfoModalProps = {
onCancel: () => void
Expand Down Expand Up @@ -135,7 +135,7 @@ const SafeAppsInfoModal = ({
/>
<Grid container justifyContent="center" alignItems="center" direction="column" textAlign="center" p={3}>
<Slider onSlideChange={handleSlideChange}>
{!isConsentAccepted && <LegalDisclaimer />}
{!isConsentAccepted && <LegalDisclaimerContent />}

{!isPermissionsReviewCompleted && (
<AllowedFeaturesList
Expand Down
9 changes: 0 additions & 9 deletions src/components/safe-apps/SafeAppsInfoModal/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,6 @@
background-size: cover;
}

.disclaimerContainer p,
.disclaimerContainer h3 {
line-height: 24px;
}

.disclaimerInner p {
text-align: justify;
}

.domainIcon {
position: relative;
top: 6px;
Expand Down
15 changes: 15 additions & 0 deletions src/features/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import useSafeInfo from '@/hooks/useSafeInfo'
import useWallet from '@/hooks/wallets/useWallet'
import BlockedAddress from '@/components/common/BlockedAddress'
import { isBlockedAddress } from '@/services/ofac'
import useSwapConsent from './useSwapConsent'
import Disclaimer from '@/components/common/Disclaimer'
import LegalDisclaimerContent from '@/components/common/LegalDisclaimerContent'

const supportedChains = [1, 100, 11155111]

Expand Down Expand Up @@ -54,6 +57,7 @@ const SwapWidget = ({ sell }: Params) => {

const { safeAddress } = useSafeInfo()
const wallet = useWallet()
const { isConsentAccepted, onAccept } = useSwapConsent()

const [toasts, setToasts] = useState<String[]>([])

Expand Down Expand Up @@ -208,6 +212,17 @@ const SwapWidget = ({ sell }: Params) => {
return <BlockedAddress address={wallet.address} />
}

if (!isConsentAccepted) {
return (
<Disclaimer
title="Legal Disclaimer"
content={<LegalDisclaimerContent withTitle={false} />}
onAccept={onAccept}
buttonText="Continue"
/>
)
}

if (!isSupportedChainForSwap(Number(chainId))) {
return (
<Container>
Expand Down
22 changes: 22 additions & 0 deletions src/features/swap/useSwapConsent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useCallback } from 'react'
import useLocalStorage from '@/services/local-storage/useLocalStorage'

const SWAPS_CONSENT_STORAGE_KEY = 'swapDisclaimerAccepted'

const useSwapConsent = (): {
isConsentAccepted: boolean
onAccept: () => void
} => {
const [isConsentAccepted = false, setIsConsentAccepted] = useLocalStorage<boolean>(SWAPS_CONSENT_STORAGE_KEY)

const onAccept = useCallback(() => {
setIsConsentAccepted(true)
}, [setIsConsentAccepted])

return {
isConsentAccepted,
onAccept,
}
}

export default useSwapConsent

0 comments on commit ef93ae5

Please sign in to comment.