From af800799572ec0455ac14a9f89f9b93a5496ae9d Mon Sep 17 00:00:00 2001 From: Jack Short Date: Fri, 29 Sep 2023 11:32:05 -0500 Subject: [PATCH] feat: remove buy button and landing terminology for uk (#7386) * feat: remove buy button and landing terminology for uk * removing tarballs * mocked setup * setting compliance to gb * turning back on defaults * cache for user * moving to hook and grid sizing * fixing tests * comments * landinage page cards * cypress test * removing extra store --- cypress/support/setupTests.ts | 5 +- package.json | 2 +- src/analytics/index.tsx | 5 + .../AccountDrawer/AuthenticatedHeader.tsx | 44 +++--- .../swap/SwapBuyFiatButton.test.tsx | 3 + src/components/swap/SwapBuyFiatButton.tsx | 8 + .../SwapBuyFiatButton.test.tsx.snap | 2 + src/hooks/useIsNotOriginCountry.ts | 7 + src/pages/App.tsx | 4 +- .../Landing/__snapshots__/index.test.tsx.snap | 92 +++++++----- src/pages/Landing/index.test.tsx | 6 + src/pages/Landing/index.tsx | 137 +++++++++++++----- src/state/reducerTypeTest.ts | 1 + src/state/user/reducer.ts | 7 + src/tracing/index.ts | 7 +- yarn.lock | 8 +- 16 files changed, 230 insertions(+), 108 deletions(-) create mode 100644 src/hooks/useIsNotOriginCountry.ts diff --git a/cypress/support/setupTests.ts b/cypress/support/setupTests.ts index c66a2869c4..7ae34a206d 100644 --- a/cypress/support/setupTests.ts +++ b/cypress/support/setupTests.ts @@ -27,7 +27,10 @@ beforeEach(() => { server_upload_time: Date.now(), payload_size_bytes: byteSize, events_ingested: req.body.events.length, - }) + }), + { + 'origin-country': 'US', + } ) }).intercept('https://*.sentry.io', { statusCode: 200 }) diff --git a/package.json b/package.json index 443202ed67..6628bf4d5f 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "@sentry/tracing": "^7.45.0", "@sentry/types": "^7.45.0", "@types/react-window-infinite-loader": "^1.0.6", - "@uniswap/analytics": "^1.4.0", + "@uniswap/analytics": "1.5.0", "@uniswap/analytics-events": "^2.24.0", "@uniswap/governance": "^1.0.2", "@uniswap/liquidity-staker": "^1.0.2", diff --git a/src/analytics/index.tsx b/src/analytics/index.tsx index f5030b13e9..35bcfaa728 100644 --- a/src/analytics/index.tsx +++ b/src/analytics/index.tsx @@ -52,3 +52,8 @@ export const sendAnalyticsEvent: typeof sendAnalyticsTraceEvent = (event, proper sendAnalyticsTraceEvent(event, properties) } } + +// This is only used for initial page load so we can get the user's country +export const sendInitializationEvent: typeof sendAnalyticsTraceEvent = (event, properties) => { + sendAnalyticsTraceEvent(event, properties) +} diff --git a/src/components/AccountDrawer/AuthenticatedHeader.tsx b/src/components/AccountDrawer/AuthenticatedHeader.tsx index 3be3675cf3..53dd6f0881 100644 --- a/src/components/AccountDrawer/AuthenticatedHeader.tsx +++ b/src/components/AccountDrawer/AuthenticatedHeader.tsx @@ -14,6 +14,7 @@ import Tooltip from 'components/Tooltip' import { getConnection } from 'connection' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import useENSName from 'hooks/useENSName' +import { useIsNotOriginCountry } from 'hooks/useIsNotOriginCountry' import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks' import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable' import { ProfilePageStateType } from 'nft/types' @@ -159,6 +160,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account const resetSellAssets = useSellAsset((state) => state.reset) const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters) const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable) + const shouldShowBuyFiatButton = useIsNotOriginCountry('GB') const { formatNumber } = useFormatter() const shouldDisableNFTRoutes = useDisableNFTRoutes() @@ -304,26 +306,28 @@ export default function AuthenticatedHeader({ account, openSettings }: { account View and sell NFTs )} - - {error ? ( - {error} - ) : ( - <> - {fiatOnrampAvailabilityLoading ? ( - - ) : ( - - )}{' '} - Buy crypto - - )} - + {shouldShowBuyFiatButton && ( + + {error ? ( + {error} + ) : ( + <> + {fiatOnrampAvailabilityLoading ? ( + + ) : ( + + )}{' '} + Buy crypto + + )} + + )} {Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && ( Not available in your region diff --git a/src/components/swap/SwapBuyFiatButton.test.tsx b/src/components/swap/SwapBuyFiatButton.test.tsx index 4075b0c7d1..7f52476cce 100644 --- a/src/components/swap/SwapBuyFiatButton.test.tsx +++ b/src/components/swap/SwapBuyFiatButton.test.tsx @@ -1,6 +1,8 @@ import userEvent from '@testing-library/user-event' import { useWeb3React } from '@web3-react/core' import { useAccountDrawer } from 'components/AccountDrawer' +import store from 'state' +import { setOriginCountry } from 'state/user/reducer' import { mocked } from 'test-utils/mocked' import { act, fireEvent, render, screen } from 'test-utils/render' @@ -45,6 +47,7 @@ describe('SwapBuyFiatButton.tsx', () => { let useOpenModal: jest.Mock beforeAll(() => { + store.dispatch(setOriginCountry('US')) toggleWalletDrawer = jest.fn() useOpenModal = jest.fn() }) diff --git a/src/components/swap/SwapBuyFiatButton.tsx b/src/components/swap/SwapBuyFiatButton.tsx index e348cef565..6100ab4256 100644 --- a/src/components/swap/SwapBuyFiatButton.tsx +++ b/src/components/swap/SwapBuyFiatButton.tsx @@ -5,9 +5,11 @@ import { TraceEvent } from 'analytics' import { useAccountDrawer } from 'components/AccountDrawer' import { ButtonText } from 'components/Button' import { MouseoverTooltip } from 'components/Tooltip' +import { useIsNotOriginCountry } from 'hooks/useIsNotOriginCountry' import { useCallback, useEffect, useState } from 'react' import styled from 'styled-components' import { ExternalLink } from 'theme/components' +import { textFadeIn } from 'theme/styles' import { useFiatOnrampAvailability, useOpenModal } from '../../state/application/hooks' import { ApplicationModal } from '../../state/application/reducer' @@ -25,6 +27,7 @@ enum BuyFiatFlowState { } const StyledTextButton = styled(ButtonText)` + ${textFadeIn} color: ${({ theme }) => theme.neutral2}; gap: 4px; font-weight: 485; @@ -39,6 +42,7 @@ const StyledTextButton = styled(ButtonText)` export default function SwapBuyFiatButton() { const { account } = useWeb3React() const openFiatOnRampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP) + const shouldShowBuyFiatButton = useIsNotOriginCountry('GB') const [checkFiatRegionAvailability, setCheckFiatRegionAvailability] = useState(false) const { available: fiatOnrampAvailable, @@ -97,6 +101,10 @@ export default function SwapBuyFiatButton() { const fiatOnRampsUnavailableTooltipDisabled = !fiatOnrampAvailabilityChecked || (fiatOnrampAvailabilityChecked && fiatOnrampAvailable) + if (!shouldShowBuyFiatButton) { + return null + } + return ( state.user.originCountry) + return Boolean(originCountry) && originCountry !== country +} diff --git a/src/pages/App.tsx b/src/pages/App.tsx index b2f7ae5a24..d7f18e7009 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -1,6 +1,6 @@ import { CustomUserProperties, getBrowser, SharedEventName } from '@uniswap/analytics-events' import { useWeb3React } from '@web3-react/core' -import { getDeviceId, sendAnalyticsEvent, Trace, user } from 'analytics' +import { getDeviceId, sendAnalyticsEvent, sendInitializationEvent, Trace, user } from 'analytics' import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' @@ -149,7 +149,7 @@ export default function App() { const serviceWorkerProperty = isServiceWorkerInstalled ? (isServiceWorkerHit ? 'hit' : 'miss') : 'uninstalled' const pageLoadProperties = { service_worker: serviceWorkerProperty } - sendAnalyticsEvent(SharedEventName.APP_LOADED, pageLoadProperties) + sendInitializationEvent(SharedEventName.APP_LOADED, pageLoadProperties) const sendWebVital = (metric: string) => ({ delta }: Metric) => diff --git a/src/pages/Landing/__snapshots__/index.test.tsx.snap b/src/pages/Landing/__snapshots__/index.test.tsx.snap index b885567047..24d7ac7424 100644 --- a/src/pages/Landing/__snapshots__/index.test.tsx.snap +++ b/src/pages/Landing/__snapshots__/index.test.tsx.snap @@ -1152,6 +1152,8 @@ exports[`disable nft on landing page does not render nft information and card 1` } .c18 { + -webkit-animation: iAjNNh 125ms ease-in; + animation: iAjNNh 125ms ease-in; color: #7D7D7D; gap: 4px; font-weight: 485; @@ -1345,6 +1347,26 @@ exports[`disable nft on landing page does not render nft information and card 1` pointer-events: auto; } +.c64 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + gap: 8px; + margin-top: 24px; + color: #7D7D7D; + -webkit-text-decoration: none; + text-decoration: none; + font-size: 16px; + line-height: 24px; + font-weight: 535; + text-align: center; +} + +.c64:hover { + color: #CECECE; +} + .c55 { color: transparent; font-size: 36px; @@ -1355,6 +1377,8 @@ exports[`disable nft on landing page does not render nft information and card 1` background: linear-gradient(10deg,rgba(255,79,184,1) 0%,rgba(255,159,251,1) 100%); background-clip: text; -webkit-background-clip: text; + -webkit-animation: iAjNNh 125ms ease-in; + animation: iAjNNh 125ms ease-in; } .c57 { @@ -1376,6 +1400,8 @@ exports[`disable nft on landing page does not render nft information and card 1` -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; + -webkit-animation: iAjNNh 125ms ease-in; + animation: iAjNNh 125ms ease-in; } .c59 { @@ -1521,26 +1547,6 @@ exports[`disable nft on landing page does not render nft information and card 1` width: 100%; } -.c64 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - gap: 8px; - margin-top: 24px; - color: #7D7D7D; - -webkit-text-decoration: none; - text-decoration: none; - font-size: 16px; - line-height: 24px; - font-weight: 535; - text-align: center; -} - -.c64:hover { - color: #CECECE; -} - @media screen and (min-width:1024px) { .c84 { -webkit-flex-direction: row; @@ -3780,6 +3786,8 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` } .c18 { + -webkit-animation: iAjNNh 125ms ease-in; + animation: iAjNNh 125ms ease-in; color: #7D7D7D; gap: 4px; font-weight: 485; @@ -3973,6 +3981,26 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` pointer-events: auto; } +.c64 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + gap: 8px; + margin-top: 24px; + color: #7D7D7D; + -webkit-text-decoration: none; + text-decoration: none; + font-size: 16px; + line-height: 24px; + font-weight: 535; + text-align: center; +} + +.c64:hover { + color: #CECECE; +} + .c55 { color: transparent; font-size: 36px; @@ -3983,6 +4011,8 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` background: linear-gradient(10deg,rgba(255,79,184,1) 0%,rgba(255,159,251,1) 100%); background-clip: text; -webkit-background-clip: text; + -webkit-animation: iAjNNh 125ms ease-in; + animation: iAjNNh 125ms ease-in; } .c57 { @@ -4004,6 +4034,8 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` -webkit-justify-content: center; -ms-flex-pack: center; justify-content: center; + -webkit-animation: iAjNNh 125ms ease-in; + animation: iAjNNh 125ms ease-in; } .c59 { @@ -4149,26 +4181,6 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` width: 100%; } -.c64 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - gap: 8px; - margin-top: 24px; - color: #7D7D7D; - -webkit-text-decoration: none; - text-decoration: none; - font-size: 16px; - line-height: 24px; - font-weight: 535; - text-align: center; -} - -.c64:hover { - color: #CECECE; -} - @media screen and (min-width:1024px) { .c85 { -webkit-flex-direction: row; diff --git a/src/pages/Landing/index.test.tsx b/src/pages/Landing/index.test.tsx index ccd48d927d..68902e8071 100644 --- a/src/pages/Landing/index.test.tsx +++ b/src/pages/Landing/index.test.tsx @@ -1,4 +1,6 @@ import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' +import store from 'state' +import { setOriginCountry } from 'state/user/reducer' import { mocked } from 'test-utils/mocked' import { render } from 'test-utils/render' @@ -7,6 +9,10 @@ import Landing from '.' jest.mock('hooks/useDisableNFTRoutes') describe('disable nft on landing page', () => { + beforeAll(() => { + store.dispatch(setOriginCountry('US')) + }) + it('renders nft information and card', () => { mocked(useDisableNFTRoutes).mockReturnValue(false) const { container } = render() diff --git a/src/pages/Landing/index.tsx b/src/pages/Landing/index.tsx index 6ebe84c1f6..2ae8edf24b 100644 --- a/src/pages/Landing/index.tsx +++ b/src/pages/Landing/index.tsx @@ -16,10 +16,11 @@ import { ArrowDownCircle } from 'react-feather' import { Navigate, useLocation, useNavigate } from 'react-router-dom' import { Link as NativeLink } from 'react-router-dom' import { useAppSelector } from 'state/hooks' +import { AppState } from 'state/reducer' import styled, { css } from 'styled-components' import { BREAKPOINTS } from 'theme' import { useIsDarkMode } from 'theme/components/ThemeToggle' -import { TRANSITION_DURATIONS } from 'theme/styles' +import { textFadeIn, TRANSITION_DURATIONS } from 'theme/styles' import { Z_INDEX } from 'theme/zIndex' import { getDownloadAppLinkProps } from 'utils/openDownloadApp' @@ -106,7 +107,23 @@ const ContentContainer = styled.div<{ isDarkMode: boolean }>` } ` -const TitleText = styled.h1<{ isDarkMode: boolean }>` +const DownloadWalletLink = styled.a` + display: inline-flex; + gap: 8px; + margin-top: 24px; + color: ${({ theme }) => theme.neutral2}; + text-decoration: none; + font-size: 16px; + line-height: 24px; + font-weight: 535; + text-align: center; + + :hover { + color: ${({ theme }) => theme.neutral3}; + } +` + +const TitleText = styled.h1<{ isDarkMode: boolean; $visible: boolean }>` color: transparent; font-size: 36px; line-height: 44px; @@ -124,6 +141,13 @@ const TitleText = styled.h1<{ isDarkMode: boolean }>` background-clip: text; -webkit-background-clip: text; + ${({ $visible }) => + $visible + ? css` + ${textFadeIn} + ` + : 'opacity: 0;'} + @media screen and (min-width: ${BREAKPOINTS.sm}px) { font-size: 48px; line-height: 56px; @@ -150,9 +174,16 @@ const SubText = styled.div` } ` -const SubTextContainer = styled.div` +const SubTextContainer = styled.div<{ $visible: boolean }>` display: flex; justify-content: center; + + ${({ $visible }) => + $visible + ? css` + ${textFadeIn} + ` + : 'opacity: 0;'} ` const LandingButton = styled(BaseButton)` @@ -303,9 +334,36 @@ export default function Landing() { const cardsRef = useRef(null) const selectedWallet = useAppSelector((state) => state.user.selectedWallet) const shouldDisableNFTRoutes = useDisableNFTRoutes() - const cards = useMemo( - () => MAIN_CARDS.filter((card) => !(shouldDisableNFTRoutes && card.to.startsWith('/nft'))), - [shouldDisableNFTRoutes] + const originCountry = useAppSelector((state: AppState) => state.user.originCountry) + const renderUkSpecificText = Boolean(originCountry) && originCountry === 'GB' + const cards = useMemo(() => { + const mainCards = MAIN_CARDS.filter( + (card) => + !(shouldDisableNFTRoutes && card.to.startsWith('/nft')) && !(card.to.startsWith('/swap') && !originCountry) + ) + + mainCards.forEach((card) => { + if (card.to.startsWith('/swap') && renderUkSpecificText) { + card.description = 'Explore tokens on Ethereum, Polygon, Optimism and more ' + card.cta = 'Discover Tokens' + } + }) + + return mainCards + }, [originCountry, renderUkSpecificText, shouldDisableNFTRoutes]) + + const extraCards = useMemo( + () => + MORE_CARDS.filter( + (card) => + !( + card.to.startsWith( + 'https://support.uniswap.org/hc/en-us/articles/11306574799117-How-to-use-Moon-Pay-on-the-Uniswap-web-app-' + ) && + (!originCountry || renderUkSpecificText) + ) + ), + [originCountry, renderUkSpecificText] ) const [accountDrawerOpen] = useAccountDrawer() @@ -320,6 +378,35 @@ export default function Landing() { const location = useLocation() const queryParams = parse(location.search, { ignoreQueryPrefix: true }) + + const titles = useMemo(() => { + if (!originCountry) { + return { + header: null, + subHeader: null, + } + } + + if (renderUkSpecificText) { + return { + header: Go direct to DeFi with Uniswap, + subHeader: Swap and explore tokens and NFTs, + } + } + + if (shouldDisableNFTRoutes) { + return { + header: Trade crypto with confidence, + subHeader: Buy, sell, and explore tokens, + } + } + + return { + header: Trade crypto and NFTs with confidence, + subHeader: Buy, sell, and explore tokens and NFTs, + } + }, [originCountry, renderUkSpecificText, shouldDisableNFTRoutes]) + if (selectedWallet && !queryParams.intro) { return } @@ -343,21 +430,11 @@ export default function Landing() { - - {shouldDisableNFTRoutes ? ( - Trade crypto with confidence - ) : ( - Trade crypto and NFTs with confidence - )} + + {titles.header} - - - {shouldDisableNFTRoutes ? ( - Buy, sell, and explore tokens - ) : ( - Buy, sell, and explore tokens and NFTs - )} - + + {titles.subHeader} ))} - - {MORE_CARDS.map(({ darkIcon, lightIcon, ...card }) => ( + + {extraCards.map(({ darkIcon, lightIcon, ...card }) => ( ))} @@ -414,19 +491,3 @@ export default function Landing() { ) } - -const DownloadWalletLink = styled.a` - display: inline-flex; - gap: 8px; - margin-top: 24px; - color: ${({ theme }) => theme.neutral2}; - text-decoration: none; - font-size: 16px; - line-height: 24px; - font-weight: 535; - text-align: center; - - :hover { - color: ${({ theme }) => theme.neutral3}; - } -` diff --git a/src/state/reducerTypeTest.ts b/src/state/reducerTypeTest.ts index 88de85217c..5aadb43231 100644 --- a/src/state/reducerTypeTest.ts +++ b/src/state/reducerTypeTest.ts @@ -91,6 +91,7 @@ interface ExpectedUserState { showSurveyPopup?: boolean disabledUniswapX?: boolean optedOutOfUniswapX?: boolean + originCountry?: string } assert>() diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index ca0c34c9e8..5e49d56723 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -54,6 +54,8 @@ export interface UserState { optedOutOfUniswapX?: boolean // undefined means has not gone through A/B split yet showSurveyPopup?: boolean + + originCountry?: string } function pairKey(token0Address: string, token1Address: string) { @@ -73,6 +75,7 @@ export const initialState: UserState = { timestamp: currentTimestamp(), hideBaseWalletBanner: false, showSurveyPopup: undefined, + originCountry: undefined, } const userSlice = createSlice({ @@ -133,12 +136,16 @@ const userSlice = createSlice({ } state.timestamp = currentTimestamp() }, + setOriginCountry(state, { payload: country }) { + state.originCountry = country + }, }, }) export const { addSerializedPair, addSerializedToken, + setOriginCountry, updateSelectedWallet, updateHideClosedPositions, updateUserRouterPreference, diff --git a/src/tracing/index.ts b/src/tracing/index.ts index a771111663..d37c0d20f2 100644 --- a/src/tracing/index.ts +++ b/src/tracing/index.ts @@ -2,8 +2,10 @@ import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' import { SharedEventName } from '@uniswap/analytics-events' import { initializeAnalytics, OriginApplication } from 'analytics' -import { isDevelopmentEnv, isSentryEnabled } from 'utils/env' -import { getEnvName, isProductionEnv } from 'utils/env' +import store from 'state' +import { setOriginCountry } from 'state/user/reducer' +import { isDevelopmentEnv, isProductionEnv, isSentryEnabled } from 'utils/env' +import { getEnvName } from 'utils/env' import { v4 as uuidv4 } from 'uuid' import { beforeSend } from './errors' @@ -45,4 +47,5 @@ initializeAnalytics(AMPLITUDE_DUMMY_KEY, OriginApplication.INTERFACE, { commitHash: process.env.REACT_APP_GIT_COMMIT_HASH, isProductionEnv: isProductionEnv(), debug: isDevelopmentEnv(), + reportOriginCountry: (country: string) => store.dispatch(setOriginCountry(country)), }) diff --git a/yarn.lock b/yarn.lock index 667fcabb1e..64e3a14593 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6050,10 +6050,10 @@ resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.24.0.tgz#c81d0c24da4f052b7f6b2663ff42bfa787be91b5" integrity sha512-MhX9L95Y7i28a3KxRFJnpmNxNHAgownBVPyhT+mu4PnCXiPEuSovml+uJr277tysKSqxRYqLnCeAw4LocTBIfg== -"@uniswap/analytics@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@uniswap/analytics/-/analytics-1.4.0.tgz#ef2a76d4eae5dd0de09d763840b1edae9466314b" - integrity sha512-906YmEwRF2FCtn/R0EsVMZCkMy+LE74HGVw9jeXbnh6MEQvu0i7yMfTroMJEu0f9KVmmXr3xDWT+ZA0/OmnDdQ== +"@uniswap/analytics@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@uniswap/analytics/-/analytics-1.5.0.tgz#44e9febf773778c74516896c2555d6f95b2c6d62" + integrity sha512-4Hj7uMlU8j/FbKoXxMFmx2pClgOywq+dKy26gwP7696m5GPY2ZFH14XvBrg/ZMJ/Q5tm6nlMDvtxV6xMUK8ung== dependencies: "@amplitude/analytics-browser" "^1.5.8" react "^18.2.0"