feat: add query param to disable the nft sections of the app (#6225)

* feat: add queryparam to disable the nft sections of the app

* fix

* include mini portfolio

* add comments explaining nft disable atom usage and suggesting future work

* add subtitle exception to landing page and correct the bool

* update comment

* comment syntax nits
This commit is contained in:
Jordan Frankfurt 2023-03-27 21:54:39 -04:00 committed by GitHub
parent a0f20c54d8
commit 048607080c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 69 deletions

@ -6,6 +6,7 @@ import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
import { chainIdToBackendName } from 'graphql/data/util' import { chainIdToBackendName } from 'graphql/data/util'
import { useIsNftPage } from 'hooks/useIsNftPage' import { useIsNftPage } from 'hooks/useIsNftPage'
import { useIsPoolsPage } from 'hooks/useIsPoolsPage' import { useIsPoolsPage } from 'hooks/useIsPoolsPage'
import { useAtomValue } from 'jotai/utils'
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'
@ -13,6 +14,7 @@ import { useProfilePageState } from 'nft/hooks'
import { ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { NavLink, NavLinkProps, useLocation, useNavigate } from 'react-router-dom' import { NavLink, NavLinkProps, useLocation, useNavigate } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { Bag } from './Bag' import { Bag } from './Bag'
@ -60,6 +62,8 @@ export const PageTabs = () => {
const isNftPage = useIsNftPage() const isNftPage = useIsNftPage()
const micrositeEnabled = useMGTMMicrositeEnabled() const micrositeEnabled = useMGTMMicrositeEnabled()
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
return ( return (
<> <>
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}> <MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
@ -68,9 +72,11 @@ export const PageTabs = () => {
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}> <MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
<Trans>Tokens</Trans> <Trans>Tokens</Trans>
</MenuItem> </MenuItem>
<MenuItem dataTestId="nft-nav" href="/nfts" isActive={isNftPage}> {!shouldDisableNFTRoutes && (
<Trans>NFTs</Trans> <MenuItem dataTestId="nft-nav" href="/nfts" isActive={isNftPage}>
</MenuItem> <Trans>NFTs</Trans>
</MenuItem>
)}
<Box display={{ sm: 'flex', lg: 'none', xxl: 'flex' }} width="full"> <Box display={{ sm: 'flex', lg: 'none', xxl: 'flex' }} width="full">
<MenuItem href="/pools" dataTestId="pool-nav-link" isActive={isPoolActive}> <MenuItem href="/pools" dataTestId="pool-nav-link" isActive={isPoolActive}>
<Trans>Pools</Trans> <Trans>Pools</Trans>

@ -12,12 +12,14 @@ import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { useGetConnection } from 'connection' import { useGetConnection } from 'connection'
import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks' import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { useAtomValue } from 'jotai/utils'
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks' import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable' import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
import { ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { ArrowDownRight, ArrowUpRight, Copy, CreditCard, IconProps, Info, Power, Settings } from 'react-feather' import { ArrowDownRight, ArrowUpRight, Copy, CreditCard, IconProps, Info, Power, Settings } from 'react-feather'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer' import { updateSelectedWallet } from 'state/user/reducer'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
@ -166,6 +168,8 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters) const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable) const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account) const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
const isUnclaimed = useUserHasAvailableClaim(account) const isUnclaimed = useUserHasAvailableClaim(account)
const getConnection = useGetConnection() const getConnection = useGetConnection()
@ -302,14 +306,16 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
</> </>
)} )}
</HeaderButton> </HeaderButton>
<HeaderButton {!shouldDisableNFTRoutes && (
data-testid="nft-view-self-nfts" <HeaderButton
onClick={navigateToProfile} data-testid="nft-view-self-nfts"
size={ButtonSize.medium} onClick={navigateToProfile}
emphasis={ButtonEmphasis.medium} size={ButtonSize.medium}
> emphasis={ButtonEmphasis.medium}
<Trans>View and sell NFTs</Trans> >
</HeaderButton> <Trans>View and sell NFTs</Trans>
</HeaderButton>
)}
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && ( {Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
<FiatOnrampNotAvailableText marginTop="8px"> <FiatOnrampNotAvailableText marginTop="8px">
<Trans>Not available in your region</Trans> <Trans>Not available in your region</Trans>

@ -5,7 +5,9 @@ import Column from 'components/Column'
import { AutoRow } from 'components/Row' import { AutoRow } from 'components/Row'
import { useMiniPortfolioEnabled } from 'featureFlags/flags/miniPortfolio' import { useMiniPortfolioEnabled } from 'featureFlags/flags/miniPortfolio'
import { useIsNftPage } from 'hooks/useIsNftPage' import { useIsNftPage } from 'hooks/useIsNftPage'
import { useAtomValue } from 'jotai/utils'
import { useState } from 'react' import { useState } from 'react'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
@ -75,27 +77,31 @@ const Pages: Array<Page> = [
function MiniPortfolio({ account }: { account: string }) { function MiniPortfolio({ account }: { account: string }) {
const isNftPage = useIsNftPage() const isNftPage = useIsNftPage()
const [currentPage, setCurrentPage] = useState(isNftPage ? 1 : 0) const [currentPage, setCurrentPage] = useState(isNftPage ? 1 : 0)
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
const Page = Pages[currentPage].component const Page = Pages[currentPage].component
return ( return (
<Wrapper> <Wrapper>
<Nav> <Nav>
{Pages.map(({ title }, index) => ( {Pages.map(({ title, loggingElementName }, index) => {
<TraceEvent if (shouldDisableNFTRoutes && loggingElementName.includes('nft')) return null
events={[BrowserEvent.onClick]} return (
name={SharedEventName.NAVBAR_CLICKED} <TraceEvent
element={Pages[index].loggingElementName} events={[BrowserEvent.onClick]}
key={index} name={SharedEventName.NAVBAR_CLICKED}
> element={loggingElementName}
<NavItem key={index}
onClick={() => setCurrentPage(index)}
active={currentPage === index}
key={`Mini Portfolio page ${index}`}
> >
{title} <NavItem
</NavItem> onClick={() => setCurrentPage(index)}
</TraceEvent> active={currentPage === index}
))} key={`Mini Portfolio page ${index}`}
>
{title}
</NavItem>
</TraceEvent>
)
})}
</Nav> </Nav>
<PageWrapper> <PageWrapper>
<Page account={account} /> <Page account={account} />

@ -6,9 +6,11 @@ import TopLevelModals from 'components/TopLevelModals'
import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useFeatureFlagsIsLoaded } from 'featureFlags'
import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm' import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
import { useAtom } from 'jotai'
import { useBag } from 'nft/hooks/useBag' import { useBag } from 'nft/hooks/useBag'
import { lazy, Suspense, useEffect, useMemo, useState } from 'react' import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
import { Navigate, Route, Routes, useLocation } from 'react-router-dom' import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { StatsigProvider, StatsigUser } from 'statsig-react' import { StatsigProvider, StatsigUser } from 'statsig-react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { SpinnerSVG } from 'theme/components' import { SpinnerSVG } from 'theme/components'
@ -132,6 +134,7 @@ const LazyLoadSpinner = () => (
export default function App() { export default function App() {
const isLoaded = useFeatureFlagsIsLoaded() const isLoaded = useFeatureFlagsIsLoaded()
const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
const { pathname } = useLocation() const { pathname } = useLocation()
const currentPage = getCurrentPageFromLocation(pathname) const currentPage = getCurrentPageFromLocation(pathname)
@ -146,6 +149,15 @@ export default function App() {
setScrolledState(false) setScrolledState(false)
}, [pathname]) }, [pathname])
const [searchParams] = useSearchParams()
useEffect(() => {
if (searchParams.get('disableNFTs') === 'true') {
setShouldDisableNFTRoutes(true)
} else if (searchParams.get('disableNFTs') === 'false') {
setShouldDisableNFTRoutes(false)
}
}, [searchParams, setShouldDisableNFTRoutes])
useEffect(() => { useEffect(() => {
// User properties *must* be set before sending corresponding event properties, // User properties *must* be set before sending corresponding event properties,
// so that the event contains the correct and up-to-date user properties. // so that the event contains the correct and up-to-date user properties.
@ -271,46 +283,54 @@ export default function App() {
<Route path="migrate/v2" element={<MigrateV2 />} /> <Route path="migrate/v2" element={<MigrateV2 />} />
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} /> <Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
<Route {!shouldDisableNFTRoutes && (
path="/nfts" <>
element={ <Route
<Suspense fallback={null}> path="/nfts"
<NftExplore /> element={
</Suspense> <Suspense fallback={null}>
} <NftExplore />
/> </Suspense>
<Route }
path="/nfts/asset/:contractAddress/:tokenId" />
element={
<Suspense fallback={null}> <Route
<Asset /> path="/nfts/asset/:contractAddress/:tokenId"
</Suspense> element={
} <Suspense fallback={null}>
/> <Asset />
<Route </Suspense>
path="/nfts/profile" }
element={ />
<Suspense fallback={null}>
<Profile /> <Route
</Suspense> path="/nfts/profile"
} element={
/> <Suspense fallback={null}>
<Route <Profile />
path="/nfts/collection/:contractAddress" </Suspense>
element={ }
<Suspense fallback={null}> />
<Collection />
</Suspense> <Route
} path="/nfts/collection/:contractAddress"
/> element={
<Route <Suspense fallback={null}>
path="/nfts/collection/:contractAddress/activity" <Collection />
element={ </Suspense>
<Suspense fallback={null}> }
<Collection /> />
</Suspense>
} <Route
/> path="/nfts/collection/:contractAddress/activity"
element={
<Suspense fallback={null}>
<Collection />
</Suspense>
}
/>
</>
)}
<Route path="*" element={<Navigate to="/not-found" replace />} /> <Route path="*" element={<Navigate to="/not-found" replace />} />
<Route path="/not-found" element={<NotFound />} /> <Route path="/not-found" element={<NotFound />} />

@ -7,12 +7,14 @@ import { MAIN_CARDS, MORE_CARDS } from 'components/About/constants'
import ProtocolBanner from 'components/About/ProtocolBanner' import ProtocolBanner from 'components/About/ProtocolBanner'
import { BaseButton } from 'components/Button' import { BaseButton } from 'components/Button'
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget' import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
import { useAtomValue } from 'jotai/utils'
import Swap from 'pages/Swap' import Swap from 'pages/Swap'
import { parse } from 'qs' import { parse } from 'qs'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { ArrowDownCircle } from 'react-feather' import { ArrowDownCircle } from 'react-feather'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { Link as NativeLink } from 'react-router-dom' import { Link as NativeLink } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { useAppSelector } from 'state/hooks' import { useAppSelector } from 'state/hooks'
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
@ -316,6 +318,8 @@ export default function Landing() {
} }
}, [navigate, selectedWallet, queryParams.intro]) }, [navigate, selectedWallet, queryParams.intro])
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
return ( return (
<Trace page={InterfacePageName.LANDING_PAGE} shouldLogImpression> <Trace page={InterfacePageName.LANDING_PAGE} shouldLogImpression>
{showContent && ( {showContent && (
@ -342,9 +346,21 @@ export default function Landing() {
<Glow /> <Glow />
</GlowContainer> </GlowContainer>
<ContentContainer isDarkMode={isDarkMode}> <ContentContainer isDarkMode={isDarkMode}>
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText> <TitleText isDarkMode={isDarkMode}>
{shouldDisableNFTRoutes ? (
<Trans>Trade crypto with confidence</Trans>
) : (
<Trans>Trade crypto and NFTs with confidence</Trans>
)}
</TitleText>
<SubTextContainer> <SubTextContainer>
<SubText>Buy, sell, and explore tokens and NFTs</SubText> <SubText>
{shouldDisableNFTRoutes ? (
<Trans>Buy, sell, and explore tokens</Trans>
) : (
<Trans>Buy, sell, and explore tokens and NFTs</Trans>
)}
</SubText>
</SubTextContainer> </SubTextContainer>
<ActionsContainer> <ActionsContainer>
<TraceEvent <TraceEvent

@ -0,0 +1,14 @@
import { atomWithStorage, createJSONStorage } from 'jotai/utils'
/*
Note:
We should consider a generic sessionStorage abstraction if this pattern becomes common. (i.e., Future promo dismissals like the tax service discounts or Fiat Onramp launch notification may use this.)
This would be something similar to the current feature flag implementation, but utilizing session instead
Motivation:
some dapp browsers need to be able to disable the NFT portion of the app in order to pass Apple's app store review
this atom persists the inclusion of the `disableNFTs=boolean` query parameter via the webview's session storage
*/
const storage = createJSONStorage(() => sessionStorage)
export const shouldDisableNFTRoutesAtom = atomWithStorage('shouldDisableNFTRoutes', false, storage)