Commit 145c96ca authored by lynn's avatar lynn Committed by GitHub

fix: tax promo banner updates (#6144)

* init

* update unit test

* fix hover states, fix landing pg behavior

* wip

* fix hook

* rename tax service hook

* fix typo

* remove left and right borders on mobile

* fix height of inner container on mobile
parent c3ae545b
...@@ -3,6 +3,7 @@ import { useWeb3React } from '@web3-react/core' ...@@ -3,6 +3,7 @@ import { useWeb3React } from '@web3-react/core'
import Web3Status from 'components/Web3Status' import Web3Status from 'components/Web3Status'
import { chainIdToBackendName } from 'graphql/data/util' import { chainIdToBackendName } from 'graphql/data/util'
import { useIsNftPage } from 'hooks/useIsNftPage' import { useIsNftPage } from 'hooks/useIsNftPage'
import { useIsPoolPage } from 'hooks/useIsPoolPage'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { UniIcon } from 'nft/components/icons' import { UniIcon } from 'nft/components/icons'
...@@ -52,12 +53,7 @@ export const PageTabs = () => { ...@@ -52,12 +53,7 @@ export const PageTabs = () => {
const { chainId: connectedChainId } = useWeb3React() const { chainId: connectedChainId } = useWeb3React()
const chainName = chainIdToBackendName(connectedChainId) const chainName = chainIdToBackendName(connectedChainId)
const isPoolActive = const isPoolActive = useIsPoolPage()
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase')
const isNftPage = useIsNftPage() const isNftPage = useIsNftPage()
return ( return (
......
...@@ -3,9 +3,9 @@ import { TraceEvent } from '@uniswap/analytics' ...@@ -3,9 +3,9 @@ import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events' import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button' import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import { bodySmall, subhead } from 'nft/css/common.css' import { bodySmall, subhead } from 'nft/css/common.css'
import { useState } from 'react' import { useCallback, useState } from 'react'
import { useCallback } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import { useTaxServiceDismissal } from 'state/user/hooks'
import { useIsDarkMode } from 'state/user/hooks' import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { opacify } from 'theme/utils' import { opacify } from 'theme/utils'
...@@ -36,7 +36,10 @@ const PopupContainer = styled.div<{ show: boolean; isDarkMode: boolean }>` ...@@ -36,7 +36,10 @@ const PopupContainer = styled.div<{ show: boolean; isDarkMode: boolean }>`
height: 156px; height: 156px;
bottom: 50px; bottom: 50px;
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) { @media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
border-style: solid none;
width: 100%; width: 100%;
border-radius: 0;
right: auto;
} }
::before { ::before {
...@@ -71,6 +74,11 @@ const InnerContainer = styled.div<{ isDarkMode: boolean }>` ...@@ -71,6 +74,11 @@ const InnerContainer = styled.div<{ isDarkMode: boolean }>`
padding: 16px; padding: 16px;
background-color: ${({ isDarkMode, theme }) => background-color: ${({ isDarkMode, theme }) =>
isDarkMode ? opacify(10, theme.accentAction) : opacify(4, theme.accentAction)}; isDarkMode ? opacify(10, theme.accentAction) : opacify(4, theme.accentAction)};
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
height: 100%;
width: 100%;
border-radius: 0;
}
` `
const Button = styled(ThemeButton)` const Button = styled(ThemeButton)`
...@@ -85,7 +93,7 @@ const TextContainer = styled.div` ...@@ -85,7 +93,7 @@ const TextContainer = styled.div`
user-select: none; user-select: none;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 70%; width: 90%;
justify-content: center; justify-content: center;
` `
...@@ -102,38 +110,46 @@ export const StyledXButton = styled(X)` ...@@ -102,38 +110,46 @@ export const StyledXButton = styled(X)`
const TAX_SERVICE_DISMISSED = 'TaxServiceToast-dismissed' const TAX_SERVICE_DISMISSED = 'TaxServiceToast-dismissed'
const MAX_RENDER_COUNT = 3
export default function TaxServiceBanner() { export default function TaxServiceBanner() {
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const [dismissals, addTaxServiceDismissal] = useTaxServiceDismissal()
const [modalOpen, setModalOpen] = useState(false) const [modalOpen, setModalOpen] = useState(false)
const sessionStorageTaxServiceDismissed = sessionStorage.getItem(TAX_SERVICE_DISMISSED) const sessionStorageTaxServiceDismissed = sessionStorage.getItem(TAX_SERVICE_DISMISSED)
if (!sessionStorageTaxServiceDismissed) { if (!sessionStorageTaxServiceDismissed) {
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'false') sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'false')
} }
const [bannerOpen, setBannerOpen] = useState(sessionStorageTaxServiceDismissed !== 'true') const [bannerOpen, setBannerOpen] = useState(
sessionStorageTaxServiceDismissed !== 'true' && dismissals < MAX_RENDER_COUNT
)
const onDismiss = useCallback(() => { const onDismiss = useCallback(() => {
setModalOpen(false) setModalOpen(false)
}, []) }, [])
const openTaxModal = useCallback(() => {
setModalOpen(true)
}, [])
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'true') sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'true')
setBannerOpen(false) setBannerOpen(false)
addTaxServiceDismissal(dismissals + 1)
}, [addTaxServiceDismissal, dismissals])
const handleLearnMoreClick = useCallback((e: any) => {
e.preventDefault()
e.stopPropagation()
setModalOpen(true)
}, []) }, [])
return ( return (
<PopupContainer show={bannerOpen} isDarkMode={isDarkMode}> <PopupContainer show={bannerOpen} isDarkMode={isDarkMode}>
<InnerContainer isDarkMode={isDarkMode}> <InnerContainer isDarkMode={isDarkMode} tabIndex={0}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<TextContainer data-testid="tax-service-description"> <TextContainer data-testid="tax-service-description">
<div className={subhead} style={{ paddingBottom: '12px' }}> <div className={subhead} style={{ paddingBottom: '12px' }}>
<Trans>Save on your crypto taxes</Trans> <Trans>Save on your crypto taxes</Trans>
</div> </div>
<div className={bodySmall} style={{ paddingBottom: '12px' }}> <div className={bodySmall} style={{ paddingBottom: '12px' }}>
<Trans>Get up to a 20% discount on CoinTracker or TokenTax.</Trans>{' '} <Trans>Uniswap Labs can save you up to 20% on CoinTracker and TokenTax</Trans>{' '}
</div> </div>
</TextContainer> </TextContainer>
<StyledXButton size={20} onClick={handleClose} /> <StyledXButton size={20} onClick={handleClose} />
...@@ -147,7 +163,10 @@ export default function TaxServiceBanner() { ...@@ -147,7 +163,10 @@ export default function TaxServiceBanner() {
<Button <Button
size={ButtonSize.small} size={ButtonSize.small}
emphasis={ButtonEmphasis.promotional} emphasis={ButtonEmphasis.promotional}
onClick={openTaxModal} onMouseDown={(e) => {
e.preventDefault()
}}
onClick={handleLearnMoreClick}
data-testid="learn-more-button" data-testid="learn-more-button"
> >
<Trans>Learn more</Trans> <Trans>Learn more</Trans>
......
...@@ -13,5 +13,5 @@ it('renders Tax Service Banner', async () => { ...@@ -13,5 +13,5 @@ it('renders Tax Service Banner', async () => {
render(<TaxServiceBanner />) render(<TaxServiceBanner />)
expect(screen.getByText('Save on your crypto taxes')).toBeInTheDocument() expect(screen.getByText('Save on your crypto taxes')).toBeInTheDocument()
expect(screen.getAllByTestId('learn-more-button')).toHaveLength(1) expect(screen.getAllByTestId('learn-more-button')).toHaveLength(1)
expect(screen.getByText('Get up to a 20% discount on CoinTracker or TokenTax.')).toBeInTheDocument() expect(screen.getByText('Uniswap Labs can save you up to 20% on CoinTracker and TokenTax')).toBeInTheDocument()
}) })
...@@ -98,7 +98,14 @@ function TaxServiceOption({ description, logo, url }: TaxServiceOptionProps) { ...@@ -98,7 +98,14 @@ function TaxServiceOption({ description, logo, url }: TaxServiceOptionProps) {
} }
> >
<a href={url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}> <a href={url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}>
<Button size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} data-testid="tax-service-option-button"> <Button
onMouseDown={(e) => {
e.preventDefault()
}}
size={ButtonSize.medium}
emphasis={ButtonEmphasis.medium}
data-testid="tax-service-option-button"
>
Get started Get started
</Button> </Button>
</a> </a>
...@@ -115,7 +122,14 @@ export default memo(function TaxServiceModal({ isOpen, onDismiss }: TaxServiceMo ...@@ -115,7 +122,14 @@ export default memo(function TaxServiceModal({ isOpen, onDismiss }: TaxServiceMo
<div className={subhead}> <div className={subhead}>
<Trans>Save on your crypto taxes</Trans> <Trans>Save on your crypto taxes</Trans>
</div> </div>
<StyledXButton size={20} onClick={onDismiss} /> <StyledXButton
size={20}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onDismiss()
}}
/>
</div> </div>
<TaxOptionContainer> <TaxOptionContainer>
<TaxServiceOption description={COINTRACKER_DESCRIPTION} logo={CointrackerFullLogo} url={COINTRACKER_URL} /> <TaxServiceOption description={COINTRACKER_DESCRIPTION} logo={CointrackerFullLogo} url={COINTRACKER_URL} />
......
...@@ -5,7 +5,10 @@ import FiatOnrampModal from 'components/FiatOnrampModal' ...@@ -5,7 +5,10 @@ import FiatOnrampModal from 'components/FiatOnrampModal'
import TaxServiceBanner from 'components/TaxServiceModal/TaxServiceBanner' import TaxServiceBanner from 'components/TaxServiceModal/TaxServiceBanner'
import { useTaxServiceBannerEnabled } from 'featureFlags/flags/taxServiceBanner' import { useTaxServiceBannerEnabled } from 'featureFlags/flags/taxServiceBanner'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck' import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { useIsPoolPage } from 'hooks/useIsPoolPage'
import { lazy } from 'react' import { lazy } from 'react'
import { useLocation } from 'react-router-dom'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks' import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer' import { ApplicationModal } from 'state/application/reducer'
...@@ -22,6 +25,12 @@ export default function TopLevelModals() { ...@@ -22,6 +25,12 @@ export default function TopLevelModals() {
const accountBlocked = Boolean(blockedAccountModalOpen && account) const accountBlocked = Boolean(blockedAccountModalOpen && account)
const taxServiceEnabled = useTaxServiceBannerEnabled() const taxServiceEnabled = useTaxServiceBannerEnabled()
const { pathname } = useLocation()
const isNftPage = useIsNftPage()
const isPoolPage = useIsPoolPage()
const isTaxModalServicePage = isNftPage || isPoolPage || pathname.startsWith('/swap')
return ( return (
<> <>
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} /> <AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
...@@ -30,7 +39,7 @@ export default function TopLevelModals() { ...@@ -30,7 +39,7 @@ export default function TopLevelModals() {
<TransactionCompleteModal /> <TransactionCompleteModal />
<AirdropModal /> <AirdropModal />
<FiatOnrampModal /> <FiatOnrampModal />
{taxServiceEnabled && <TaxServiceBanner />} {taxServiceEnabled && isTaxModalServicePage && <TaxServiceBanner />}
</> </>
) )
} }
...@@ -21,7 +21,7 @@ import { useNavigate } from 'react-router-dom' ...@@ -21,7 +21,7 @@ import { useNavigate } from 'react-router-dom'
import { useCurrencyBalanceString } from 'state/connection/hooks' import { useCurrencyBalanceString } from 'state/connection/hooks'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer' import { updateSelectedWallet } from 'state/user/reducer'
import styled, { css, keyframes } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme' import { ExternalLink, ThemedText } from 'theme'
import { shortenAddress } from '../../nft/utils/address' import { shortenAddress } from '../../nft/utils/address'
...@@ -31,21 +31,6 @@ import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/cl ...@@ -31,21 +31,6 @@ import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/cl
import StatusIcon from '../Identicon/StatusIcon' import StatusIcon from '../Identicon/StatusIcon'
import IconButton, { IconHoverText } from './IconButton' import IconButton, { IconHoverText } from './IconButton'
const BuyCryptoButtonBorderKeyframes = keyframes`
0% {
border-color: transparent;
}
33% {
border-color: hsla(225, 95%, 63%, 1);
}
66% {
border-color: hsla(267, 95%, 63%, 1);
}
100% {
border-color: transparent;
}
`
const BuyCryptoButton = styled(ThemeButton)` const BuyCryptoButton = styled(ThemeButton)`
border-color: transparent; border-color: transparent;
border-radius: 12px; border-radius: 12px;
...@@ -53,12 +38,6 @@ const BuyCryptoButton = styled(ThemeButton)` ...@@ -53,12 +38,6 @@ const BuyCryptoButton = styled(ThemeButton)`
border-width: 1px; border-width: 1px;
height: 40px; height: 40px;
margin-top: 8px; margin-top: 8px;
animation-direction: alternate;
animation-duration: ${({ theme }) => theme.transition.duration.slow};
animation-fill-mode: none;
animation-iteration-count: 2;
animation-name: ${BuyCryptoButtonBorderKeyframes};
animation-timing-function: ${({ theme }) => theme.transition.timing.inOut};
` `
const WalletButton = styled(ThemeButton)` const WalletButton = styled(ThemeButton)`
border-radius: 12px; border-radius: 12px;
......
import { useLocation } from 'react-router-dom'
export function useIsPoolPage() {
const { pathname } = useLocation()
return (
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase')
)
}
...@@ -33,7 +33,7 @@ export const sentryEnhancer = Sentry.createReduxEnhancer({ ...@@ -33,7 +33,7 @@ export const sentryEnhancer = Sentry.createReduxEnhancer({
popupList: application.popupList, popupList: application.popupList,
}, },
user: { user: {
fiatOnrampAcknowledgments: user.fiatOnrampAcknowledgments, taxServiceDismissals: user.taxServiceDismissals,
selectedWallet: user.selectedWallet, selectedWallet: user.selectedWallet,
lastUpdateVersionTimestamp: user.lastUpdateVersionTimestamp, lastUpdateVersionTimestamp: user.lastUpdateVersionTimestamp,
matchesDarkMode: user.matchesDarkMode, matchesDarkMode: user.matchesDarkMode,
......
...@@ -18,6 +18,7 @@ import { ...@@ -18,6 +18,7 @@ import {
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
updateHideClosedPositions, updateHideClosedPositions,
updateTaxServiceAcknowledgments,
updateUserClientSideRouter, updateUserClientSideRouter,
updateUserDarkMode, updateUserDarkMode,
updateUserDeadline, updateUserDeadline,
...@@ -92,6 +93,18 @@ export function useIsExpertMode(): boolean { ...@@ -92,6 +93,18 @@ export function useIsExpertMode(): boolean {
return useAppSelector((state) => state.user.userExpertMode) return useAppSelector((state) => state.user.userExpertMode)
} }
export function useTaxServiceDismissal(): [number, (dismissals: number) => void] {
const dispatch = useAppDispatch()
const taxServiceDismissals = useAppSelector((state) => state.user.taxServiceDismissals)
const setDismissals = useCallback(
(dismissals: number) => {
dispatch(updateTaxServiceAcknowledgments({ taxServiceDismissals: dismissals }))
},
[dispatch]
)
return [taxServiceDismissals, setDismissals]
}
export function useExpertModeManager(): [boolean, () => void] { export function useExpertModeManager(): [boolean, () => void] {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const expertMode = useIsExpertMode() const expertMode = useIsExpertMode()
......
...@@ -9,7 +9,7 @@ import { SerializedPair, SerializedToken } from './types' ...@@ -9,7 +9,7 @@ import { SerializedPair, SerializedToken } from './types'
const currentTimestamp = () => new Date().getTime() const currentTimestamp = () => new Date().getTime()
export interface UserState { export interface UserState {
fiatOnrampAcknowledgments: { renderCount: number; system: boolean; user: boolean } taxServiceDismissals: number
selectedWallet?: ConnectionType selectedWallet?: ConnectionType
...@@ -60,7 +60,7 @@ function pairKey(token0Address: string, token1Address: string) { ...@@ -60,7 +60,7 @@ function pairKey(token0Address: string, token1Address: string) {
} }
export const initialState: UserState = { export const initialState: UserState = {
fiatOnrampAcknowledgments: { renderCount: 0, system: false, user: false }, taxServiceDismissals: 0,
selectedWallet: undefined, selectedWallet: undefined,
matchesDarkMode: false, matchesDarkMode: false,
userDarkMode: null, userDarkMode: null,
...@@ -82,6 +82,9 @@ const userSlice = createSlice({ ...@@ -82,6 +82,9 @@ const userSlice = createSlice({
name: 'user', name: 'user',
initialState, initialState,
reducers: { reducers: {
updateTaxServiceAcknowledgments(state, action) {
state.taxServiceDismissals = action.payload.taxServiceDismissals
},
updateSelectedWallet(state, { payload: { wallet } }) { updateSelectedWallet(state, { payload: { wallet } }) {
state.selectedWallet = wallet state.selectedWallet = wallet
}, },
...@@ -175,6 +178,7 @@ const userSlice = createSlice({ ...@@ -175,6 +178,7 @@ const userSlice = createSlice({
export const { export const {
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
updateTaxServiceAcknowledgments,
updateSelectedWallet, updateSelectedWallet,
updateHideClosedPositions, updateHideClosedPositions,
updateMatchesDarkMode, updateMatchesDarkMode,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment