feat: uk disclaimer banner (#7428)

* feat: uk disclaimer banner

* bad merge with sitemap

* button

* cypress test

* intercept ordering

* comments

* sitemap was committed idk why

* font weights

* moving uk disclaimer

* removing trash
This commit is contained in:
Jack Short 2023-10-06 11:00:07 -07:00 committed by GitHub
parent 48379c66ce
commit 6e4746a7fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 195 additions and 6 deletions

@ -39,4 +39,30 @@ describe('Landing Page', () => {
cy.get(getTestSelector('pool-nav-link')).last().click() cy.get(getTestSelector('pool-nav-link')).last().click()
cy.url().should('include', '/pools') cy.url().should('include', '/pools')
}) })
it('does not render uk compliance banner in US', () => {
cy.visit('/swap')
cy.contains('UK disclaimer').should('not.exist')
})
it('renders uk compliance banner in uk', () => {
cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => {
const requestBody = JSON.stringify(req.body)
const byteSize = new Blob([requestBody]).size
req.alias = 'amplitude'
req.reply(
JSON.stringify({
code: 200,
server_upload_time: Date.now(),
payload_size_bytes: byteSize,
events_ingested: req.body.events.length,
}),
{
'origin-country': 'GB',
}
)
})
cy.visit('/swap')
cy.contains('UK disclaimer')
})
}) })

@ -0,0 +1,82 @@
import { t, Trans } from '@lingui/macro'
import { useOpenModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components'
import { ButtonText, ThemedText } from 'theme/components'
import { Z_INDEX } from 'theme/zIndex'
export const UK_BANNER_HEIGHT = 64
export const UK_BANNER_HEIGHT_MD = 112
export const UK_BANNER_HEIGHT_SM = 136
const BannerWrapper = styled.div`
position: relative;
display: flex;
background-color: ${({ theme }) => theme.surface1};
padding: 20px;
border-bottom: 1px solid ${({ theme }) => theme.surface3};
z-index: ${Z_INDEX.fixed};
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
flex-direction: column;
}
`
const BannerTextWrapper = styled(ThemedText.BodySecondary)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
@supports (-webkit-line-clamp: 2) {
white-space: initial;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
@supports (-webkit-line-clamp: 3) {
white-space: initial;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
}
`
const ReadMoreWrapper = styled(ButtonText)`
flex-shrink: 0;
width: max-content;
:focus {
text-decoration: none;
}
`
export const bannerText = t`
This web application is provided as a tool for users to interact with the Uniswap Protocol on
their own initiative, with no endorsement or recommendation of cryptocurrency trading activities. In doing so,
Uniswap is not recommending that users or potential users engage in cryptoasset trading activity, and users or
potential users of the web application should not regard this webpage or its contents as involving any form of
recommendation, invitation or inducement to deal in cryptoassets.
`
export function UkBanner() {
const openDisclaimer = useOpenModal(ApplicationModal.UK_DISCLAIMER)
return (
<BannerWrapper>
<BannerTextWrapper lineHeight="24px">{t`UK disclaimer:` + ' ' + bannerText}</BannerTextWrapper>
<ReadMoreWrapper>
<ThemedText.BodySecondary lineHeight="24px" color="accent1" onClick={openDisclaimer}>
<Trans>Read more</Trans>
</ThemedText.BodySecondary>
</ReadMoreWrapper>
</BannerWrapper>
)
}

@ -0,0 +1,62 @@
import { Trans } from '@lingui/macro'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import Column from 'components/Column'
import Modal from 'components/Modal'
import { X } from 'react-feather'
import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components'
import { ButtonText, ThemedText } from 'theme/components'
import { bannerText } from './UkBanner'
const Wrapper = styled(Column)`
padding: 8px;
`
const ButtonContainer = styled(Column)`
padding: 8px 12px 4px;
`
const CloseIconWrapper = styled(ButtonText)`
display: flex;
color: ${({ theme }) => theme.neutral1};
justify-content: flex-end;
padding: 8px 0px 4px;
:focus {
text-decoration: none;
}
`
const StyledThemeButton = styled(ThemeButton)`
width: 100%;
`
export function UkDisclaimerModal() {
const isOpen = useModalIsOpen(ApplicationModal.UK_DISCLAIMER)
const closeModal = useCloseModal()
return (
<Modal isOpen={isOpen} onDismiss={closeModal}>
<Wrapper gap="md">
<CloseIconWrapper onClick={() => closeModal()}>
<X size={24} />
</CloseIconWrapper>
<Column gap="sm">
<ThemedText.HeadlineLarge padding="0px 8px" fontSize="24px" lineHeight="32px">
<Trans>Disclaimer for UK residents</Trans>
</ThemedText.HeadlineLarge>
<ThemedText.BodyPrimary padding="8px 8px 12px" lineHeight="24px">
{bannerText}
</ThemedText.BodyPrimary>
</Column>
<ButtonContainer gap="md">
<StyledThemeButton size={ButtonSize.large} emphasis={ButtonEmphasis.medium} onClick={() => closeModal()}>
<Trans>Dismiss</Trans>
</StyledThemeButton>
</ButtonContainer>
</Wrapper>
</Modal>
)
}

@ -6,6 +6,7 @@ import BaseAnnouncementBanner from 'components/Banner/BaseAnnouncementBanner'
import AddressClaimModal from 'components/claim/AddressClaimModal' import AddressClaimModal from 'components/claim/AddressClaimModal'
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked' import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
import FiatOnrampModal from 'components/FiatOnrampModal' import FiatOnrampModal from 'components/FiatOnrampModal'
import { UkDisclaimerModal } from 'components/NavBar/UkDisclaimerModal'
import DevFlagsBox from 'dev/DevFlagsBox' import DevFlagsBox from 'dev/DevFlagsBox'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck' import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import Bag from 'nft/components/bag/Bag' import Bag from 'nft/components/bag/Bag'
@ -34,6 +35,7 @@ export default function TopLevelModals() {
<TransactionCompleteModal /> <TransactionCompleteModal />
<AirdropModal /> <AirdropModal />
<FiatOnrampModal /> <FiatOnrampModal />
<UkDisclaimerModal />
{shouldShowDevFlags && <DevFlagsBox />} {shouldShowDevFlags && <DevFlagsBox />}
</> </>
) )

@ -4,6 +4,7 @@ import { getDeviceId, sendAnalyticsEvent, sendInitializationEvent, Trace, user }
import ErrorBoundary from 'components/ErrorBoundary' import ErrorBoundary from 'components/ErrorBoundary'
import Loader from 'components/Icons/LoadingSpinner' import Loader from 'components/Icons/LoadingSpinner'
import NavBar, { PageTabs } from 'components/NavBar' import NavBar, { PageTabs } from 'components/NavBar'
import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner'
import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useFeatureFlagsIsLoaded } from 'featureFlags'
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
@ -11,6 +12,8 @@ import { useBag } from 'nft/hooks/useBag'
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { useAppSelector } from 'state/hooks'
import { AppState } from 'state/reducer'
import { RouterPreference } from 'state/routing/types' import { RouterPreference } from 'state/routing/types'
import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks'
import { StatsigProvider, StatsigUser } from 'statsig-react' import { StatsigProvider, StatsigUser } from 'statsig-react'
@ -60,15 +63,23 @@ const MobileBottomBar = styled.div`
} }
` `
const HeaderWrapper = styled.div<{ transparent?: boolean }>` const HeaderWrapper = styled.div<{ transparent?: boolean; bannerIsVisible?: boolean; scrollY: number }>`
${flexRowNoWrap}; ${flexRowNoWrap};
background-color: ${({ theme, transparent }) => !transparent && theme.surface1}; background-color: ${({ theme, transparent }) => !transparent && theme.surface1};
border-bottom: ${({ theme, transparent }) => !transparent && `1px solid ${theme.surface3}`}; border-bottom: ${({ theme, transparent }) => !transparent && `1px solid ${theme.surface3}`};
width: 100%; width: 100%;
justify-content: space-between; justify-content: space-between;
position: fixed; position: fixed;
top: 0; top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT - scrollY, 0) : 0)}px;
z-index: ${Z_INDEX.dropdown}; z-index: ${Z_INDEX.dropdown};
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT_MD - scrollY, 0) : 0)}px;
}
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT_SM - scrollY, 0) : 0)}px;
}
` `
export default function App() { export default function App() {
@ -80,14 +91,18 @@ export default function App() {
const currentPage = getCurrentPageFromLocation(pathname) const currentPage = getCurrentPageFromLocation(pathname)
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const [routerPreference] = useRouterPreference() const [routerPreference] = useRouterPreference()
const [scrolledState, setScrolledState] = useState(false) const [scrollY, setScrollY] = useState(0)
const scrolledState = scrollY > 0
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
const routerConfig = useRouterConfig() const routerConfig = useRouterConfig()
const originCountry = useAppSelector((state: AppState) => state.user.originCountry)
const renderUkBannner = Boolean(originCountry) && originCountry === 'GB'
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0) window.scrollTo(0, 0)
setScrolledState(false) setScrollY(0)
}, [pathname]) }, [pathname])
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
@ -148,7 +163,7 @@ export default function App() {
useEffect(() => { useEffect(() => {
const scrollListener = () => { const scrollListener = () => {
setScrolledState(window.scrollY > 0) setScrollY(window.scrollY)
} }
window.addEventListener('scroll', scrollListener) window.addEventListener('scroll', scrollListener)
return () => window.removeEventListener('scroll', scrollListener) return () => window.removeEventListener('scroll', scrollListener)
@ -198,7 +213,8 @@ export default function App() {
api: process.env.REACT_APP_STATSIG_PROXY_URL, api: process.env.REACT_APP_STATSIG_PROXY_URL,
}} }}
> >
<HeaderWrapper transparent={isHeaderTransparent}> {renderUkBannner && <UkBanner />}
<HeaderWrapper transparent={isHeaderTransparent} bannerIsVisible={renderUkBannner} scrollY={scrollY}>
<NavBar blur={isHeaderTransparent} /> <NavBar blur={isHeaderTransparent} />
</HeaderWrapper> </HeaderWrapper>
<BodyWrapper> <BodyWrapper>

@ -43,6 +43,7 @@ export enum ApplicationModal {
TAX_SERVICE, TAX_SERVICE,
TIME_SELECTOR, TIME_SELECTOR,
VOTE, VOTE,
UK_DISCLAIMER,
UNISWAP_NFT_AIRDROP_CLAIM, UNISWAP_NFT_AIRDROP_CLAIM,
} }