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'
import Web3Status from 'components/Web3Status'
import { chainIdToBackendName } from 'graphql/data/util'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { useIsPoolPage } from 'hooks/useIsPoolPage'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import { UniIcon } from 'nft/components/icons'
......@@ -52,12 +53,7 @@ export const PageTabs = () => {
const { chainId: connectedChainId } = useWeb3React()
const chainName = chainIdToBackendName(connectedChainId)
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase')
const isPoolActive = useIsPoolPage()
const isNftPage = useIsNftPage()
return (
......
......@@ -3,9 +3,9 @@ import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import { bodySmall, subhead } from 'nft/css/common.css'
import { useState } from 'react'
import { useCallback } from 'react'
import { useCallback, useState } from 'react'
import { X } from 'react-feather'
import { useTaxServiceDismissal } from 'state/user/hooks'
import { useIsDarkMode } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { opacify } from 'theme/utils'
......@@ -36,7 +36,10 @@ const PopupContainer = styled.div<{ show: boolean; isDarkMode: boolean }>`
height: 156px;
bottom: 50px;
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
border-style: solid none;
width: 100%;
border-radius: 0;
right: auto;
}
::before {
......@@ -71,6 +74,11 @@ const InnerContainer = styled.div<{ isDarkMode: boolean }>`
padding: 16px;
background-color: ${({ isDarkMode, theme }) =>
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)`
......@@ -85,7 +93,7 @@ const TextContainer = styled.div`
user-select: none;
display: flex;
flex-direction: column;
width: 70%;
width: 90%;
justify-content: center;
`
......@@ -102,38 +110,46 @@ export const StyledXButton = styled(X)`
const TAX_SERVICE_DISMISSED = 'TaxServiceToast-dismissed'
const MAX_RENDER_COUNT = 3
export default function TaxServiceBanner() {
const isDarkMode = useIsDarkMode()
const [dismissals, addTaxServiceDismissal] = useTaxServiceDismissal()
const [modalOpen, setModalOpen] = useState(false)
const sessionStorageTaxServiceDismissed = sessionStorage.getItem(TAX_SERVICE_DISMISSED)
if (!sessionStorageTaxServiceDismissed) {
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(() => {
setModalOpen(false)
}, [])
const openTaxModal = useCallback(() => {
setModalOpen(true)
}, [])
const handleClose = useCallback(() => {
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'true')
setBannerOpen(false)
addTaxServiceDismissal(dismissals + 1)
}, [addTaxServiceDismissal, dismissals])
const handleLearnMoreClick = useCallback((e: any) => {
e.preventDefault()
e.stopPropagation()
setModalOpen(true)
}, [])
return (
<PopupContainer show={bannerOpen} isDarkMode={isDarkMode}>
<InnerContainer isDarkMode={isDarkMode}>
<InnerContainer isDarkMode={isDarkMode} tabIndex={0}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<TextContainer data-testid="tax-service-description">
<div className={subhead} style={{ paddingBottom: '12px' }}>
<Trans>Save on your crypto taxes</Trans>
</div>
<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>
</TextContainer>
<StyledXButton size={20} onClick={handleClose} />
......@@ -147,7 +163,10 @@ export default function TaxServiceBanner() {
<Button
size={ButtonSize.small}
emphasis={ButtonEmphasis.promotional}
onClick={openTaxModal}
onMouseDown={(e) => {
e.preventDefault()
}}
onClick={handleLearnMoreClick}
data-testid="learn-more-button"
>
<Trans>Learn more</Trans>
......
......@@ -13,5 +13,5 @@ it('renders Tax Service Banner', async () => {
render(<TaxServiceBanner />)
expect(screen.getByText('Save on your crypto taxes')).toBeInTheDocument()
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) {
}
>
<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
</Button>
</a>
......@@ -115,7 +122,14 @@ export default memo(function TaxServiceModal({ isOpen, onDismiss }: TaxServiceMo
<div className={subhead}>
<Trans>Save on your crypto taxes</Trans>
</div>
<StyledXButton size={20} onClick={onDismiss} />
<StyledXButton
size={20}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onDismiss()
}}
/>
</div>
<TaxOptionContainer>
<TaxServiceOption description={COINTRACKER_DESCRIPTION} logo={CointrackerFullLogo} url={COINTRACKER_URL} />
......
......@@ -5,7 +5,10 @@ import FiatOnrampModal from 'components/FiatOnrampModal'
import TaxServiceBanner from 'components/TaxServiceModal/TaxServiceBanner'
import { useTaxServiceBannerEnabled } from 'featureFlags/flags/taxServiceBanner'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { useIsPoolPage } from 'hooks/useIsPoolPage'
import { lazy } from 'react'
import { useLocation } from 'react-router-dom'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
......@@ -22,6 +25,12 @@ export default function TopLevelModals() {
const accountBlocked = Boolean(blockedAccountModalOpen && account)
const taxServiceEnabled = useTaxServiceBannerEnabled()
const { pathname } = useLocation()
const isNftPage = useIsNftPage()
const isPoolPage = useIsPoolPage()
const isTaxModalServicePage = isNftPage || isPoolPage || pathname.startsWith('/swap')
return (
<>
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
......@@ -30,7 +39,7 @@ export default function TopLevelModals() {
<TransactionCompleteModal />
<AirdropModal />
<FiatOnrampModal />
{taxServiceEnabled && <TaxServiceBanner />}
{taxServiceEnabled && isTaxModalServicePage && <TaxServiceBanner />}
</>
)
}
......@@ -21,7 +21,7 @@ import { useNavigate } from 'react-router-dom'
import { useCurrencyBalanceString } from 'state/connection/hooks'
import { useAppDispatch } from 'state/hooks'
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 { shortenAddress } from '../../nft/utils/address'
......@@ -31,21 +31,6 @@ import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/cl
import StatusIcon from '../Identicon/StatusIcon'
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)`
border-color: transparent;
border-radius: 12px;
......@@ -53,12 +38,6 @@ const BuyCryptoButton = styled(ThemeButton)`
border-width: 1px;
height: 40px;
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)`
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({
popupList: application.popupList,
},
user: {
fiatOnrampAcknowledgments: user.fiatOnrampAcknowledgments,
taxServiceDismissals: user.taxServiceDismissals,
selectedWallet: user.selectedWallet,
lastUpdateVersionTimestamp: user.lastUpdateVersionTimestamp,
matchesDarkMode: user.matchesDarkMode,
......
......@@ -18,6 +18,7 @@ import {
addSerializedPair,
addSerializedToken,
updateHideClosedPositions,
updateTaxServiceAcknowledgments,
updateUserClientSideRouter,
updateUserDarkMode,
updateUserDeadline,
......@@ -92,6 +93,18 @@ export function useIsExpertMode(): boolean {
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] {
const dispatch = useAppDispatch()
const expertMode = useIsExpertMode()
......
......@@ -9,7 +9,7 @@ import { SerializedPair, SerializedToken } from './types'
const currentTimestamp = () => new Date().getTime()
export interface UserState {
fiatOnrampAcknowledgments: { renderCount: number; system: boolean; user: boolean }
taxServiceDismissals: number
selectedWallet?: ConnectionType
......@@ -60,7 +60,7 @@ function pairKey(token0Address: string, token1Address: string) {
}
export const initialState: UserState = {
fiatOnrampAcknowledgments: { renderCount: 0, system: false, user: false },
taxServiceDismissals: 0,
selectedWallet: undefined,
matchesDarkMode: false,
userDarkMode: null,
......@@ -82,6 +82,9 @@ const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
updateTaxServiceAcknowledgments(state, action) {
state.taxServiceDismissals = action.payload.taxServiceDismissals
},
updateSelectedWallet(state, { payload: { wallet } }) {
state.selectedWallet = wallet
},
......@@ -175,6 +178,7 @@ const userSlice = createSlice({
export const {
addSerializedPair,
addSerializedToken,
updateTaxServiceAcknowledgments,
updateSelectedWallet,
updateHideClosedPositions,
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