feat: path-based routing (#7275)

This commit is contained in:
Zach Pomerantz 2023-09-08 10:43:59 -07:00 committed by GitHub
parent 44e3b87ae1
commit 63bf1c0ac8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 120 additions and 111 deletions

@ -69,10 +69,10 @@ Other things to note:
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2. The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
- Swap on Uniswap V2: <https://app.uniswap.org/#/swap?use=v2> - Swap on Uniswap V2: <https://app.uniswap.org/swap?use=v2>
- View V2 liquidity: <https://app.uniswap.org/#/pools/v2> - View V2 liquidity: <https://app.uniswap.org/pools/v2>
- Add V2 liquidity: <https://app.uniswap.org/#/add/v2> - Add V2 liquidity: <https://app.uniswap.org/add/v2>
- Migrate V2 liquidity to V3: <https://app.uniswap.org/#/migrate/v2> - Migrate V2 liquidity to V3: <https://app.uniswap.org/migrate/v2>
## Accessing Uniswap V1 ## Accessing Uniswap V1

@ -12,7 +12,7 @@ describe('Testing nfts', () => {
}) })
it('should load pudgy penguin collection page', () => { it('should load pudgy penguin collection page', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`) cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-collection-asset')).should('exist') cy.get(getTestSelector('nft-collection-asset')).should('exist')
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('not.exist') cy.get(getTestSelector('nft-collection-filter-buy-now')).should('not.exist')
cy.get(getTestSelector('nft-filter')).first().click() cy.get(getTestSelector('nft-filter')).first().click()
@ -20,13 +20,13 @@ describe('Testing nfts', () => {
}) })
it('should be able to navigate to activity', () => { it('should be able to navigate to activity', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`) cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-activity')).first().click() cy.get(getTestSelector('nft-activity')).first().click()
cy.get(getTestSelector('nft-activity-row')).should('exist') cy.get(getTestSelector('nft-activity-row')).should('exist')
}) })
it('should go to the details page', () => { it('should go to the details page', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`) cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-filter')).first().click() cy.get(getTestSelector('nft-filter')).first().click()
cy.get(getTestSelector('nft-collection-filter-buy-now')).click() cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
cy.get(getTestSelector('nft-collection-asset')).first().click() cy.get(getTestSelector('nft-collection-asset')).first().click()
@ -37,7 +37,7 @@ describe('Testing nfts', () => {
}) })
it('should toggle buy now on details page', () => { it('should toggle buy now on details page', () => {
cy.visit(`/#/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`) cy.visit(`/nfts/collection/${PUDGY_COLLECTION_ADDRESS}`)
cy.get(getTestSelector('nft-filter')).first().click() cy.get(getTestSelector('nft-filter')).first().click()
cy.get(getTestSelector('nft-collection-filter-buy-now')).click() cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
cy.get(getTestSelector('nft-collection-asset')).first().click() cy.get(getTestSelector('nft-collection-asset')).first().click()

@ -69,8 +69,6 @@ describe('Token explore', () => {
cy.get(getTestSelector('tokens-network-filter-selected')).click() cy.get(getTestSelector('tokens-network-filter-selected')).click()
cy.get(getTestSelector('tokens-network-filter-option-optimism')).click() cy.get(getTestSelector('tokens-network-filter-option-optimism')).click()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism') cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism')
cy.reload()
cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism')
cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', 'Ethereum') cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', 'Ethereum')
}) })
}) })

@ -30,7 +30,7 @@ describe('Universal search bar', () => {
.and('contain.text', '$') .and('contain.text', '$')
.and('contain.text', '%') .and('contain.text', '%')
.click() .click()
cy.location('hash').should('equal', '#/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984') cy.location('pathname').should('equal', '/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
openSearch() openSearch()
cy.get(getTestSelector('searchbar-dropdown')) cy.get(getTestSelector('searchbar-dropdown'))

@ -32,11 +32,11 @@ describe('Wallet Dropdown', () => {
} }
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true }) cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
cy.location('hash').should('match', /\?lng=af-ZA$/) cy.location('search').should('match', /\?lng=af-ZA$/)
cy.contains('Uniswap available in: English') cy.contains('Uniswap available in: English')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true }) cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.location('hash').should('match', /\?lng=en-US$/) cy.location('search').should('match', /\?lng=en-US$/)
cy.contains('Uniswap available in: English').should('not.exist') cy.contains('Uniswap available in: English').should('not.exist')
}) })
} }
@ -153,6 +153,8 @@ describe('Wallet Dropdown', () => {
cy.contains('USD') cy.contains('USD')
cy.visit('/?cur=AUD', { featureFlags: [FeatureFlag.currencyConversion] }) cy.visit('/?cur=AUD', { featureFlags: [FeatureFlag.currencyConversion] })
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click()
cy.contains('AUD') cy.contains('AUD')
}) })
@ -164,11 +166,11 @@ describe('Wallet Dropdown', () => {
cy.get(getTestSelector('local-currency-settings-button')).click() cy.get(getTestSelector('local-currency-settings-button')).click()
cy.get(getTestSelector('wallet-local-currency-item')).contains('AUD').click({ force: true }) cy.get(getTestSelector('wallet-local-currency-item')).contains('AUD').click({ force: true })
cy.location('hash').should('match', /\?cur=AUD$/) cy.location('search').should('match', /\?cur=AUD$/)
cy.contains('AUD') cy.contains('AUD')
cy.get(getTestSelector('wallet-local-currency-item')).contains('USD').click({ force: true }) cy.get(getTestSelector('wallet-local-currency-item')).contains('USD').click({ force: true })
cy.location('hash').should('match', /\?cur=USD$/) cy.location('search').should('match', /\?cur=USD$/)
cy.contains('USD') cy.contains('USD')
}) })
}) })

@ -12,7 +12,7 @@ describe('translations', () => {
cy.get(getTestSelector('web3-status-connected')).click() cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-settings')).click() cy.get(getTestSelector('wallet-settings')).click()
cy.get(getTestSelector('wallet-language-item')).contains('français').click({ force: true }) cy.get(getTestSelector('wallet-language-item')).contains('français').click({ force: true })
cy.location('hash').should('match', /\?lng=fr-FR$/) cy.location('search').should('match', /\?lng=fr-FR$/)
cy.contains('Échanger') cy.contains('Échanger')
cy.contains('Uniswap disponible en : English') cy.contains('Uniswap disponible en : English')
}) })

@ -41,16 +41,13 @@ Cypress.Commands.overwrite(
(original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => { (original, url: string | Partial<Cypress.VisitOptions>, options?: Partial<Cypress.VisitOptions>) => {
if (typeof url !== 'string') throw new Error('Invalid arguments. The first argument to cy.visit must be the path.') if (typeof url !== 'string') throw new Error('Invalid arguments. The first argument to cy.visit must be the path.')
// Add a hash in the URL if it is not present (to use hash-based routing correctly with queryParams).
const hashUrl = url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url
return cy return cy
.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }) .intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 })
.provider() .provider()
.then((provider) => .then((provider) =>
original({ original({
...options, ...options,
url: hashUrl, url,
onBeforeLoad(win) { onBeforeLoad(win) {
options?.onBeforeLoad?.(win) options?.onBeforeLoad?.(win)

@ -21,7 +21,7 @@
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start", "start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
"build": "craco build", "build": "craco build",
"analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped", "analyze": "source-map-explorer 'build/static/js/*.js' --only-mapped",
"serve": "serve build -l 3000", "serve": "serve build -s -l 3000",
"lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .", "lint": "yarn eslint --ignore-path .gitignore --cache --cache-location node_modules/.cache/eslint/ .",
"typecheck": "tsc", "typecheck": "tsc",
"typecheck:cloud": "tsc -p functions/tsconfig.json", "typecheck:cloud": "tsc -p functions/tsconfig.json",
@ -105,7 +105,6 @@
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24", "@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/react-table": "^7.7.12", "@types/react-table": "^7.7.12",
"@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2", "@types/react-window": "^1.8.2",

@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useHref } from 'react-router-dom'
import { useCloseModal, useModalIsOpen } from 'state/application/hooks' import { useCloseModal, useModalIsOpen } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer' import { ApplicationModal } from 'state/application/reducer'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
@ -79,6 +80,8 @@ export default function FiatOnrampModal() {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const swapUrl = useHref('/swap')
const fetchSignedIframeUrl = useCallback(async () => { const fetchSignedIframeUrl = useCallback(async () => {
if (!account) { if (!account) {
setError('Please connect an account before making a purchase.') setError('Please connect an account before making a purchase.')
@ -98,7 +101,7 @@ export default function FiatOnrampModal() {
theme: isDarkMode ? 'dark' : 'light', theme: isDarkMode ? 'dark' : 'light',
colorCode: theme.accent1, colorCode: theme.accent1,
defaultCurrencyCode: 'eth', defaultCurrencyCode: 'eth',
redirectUrl: 'https://app.uniswap.org/#/swap', redirectUrl: swapUrl,
walletAddresses: JSON.stringify( walletAddresses: JSON.stringify(
MOONPAY_SUPPORTED_CURRENCY_CODES.reduce( MOONPAY_SUPPORTED_CURRENCY_CODES.reduce(
(acc, currencyCode) => ({ (acc, currencyCode) => ({
@ -118,7 +121,7 @@ export default function FiatOnrampModal() {
} finally { } finally {
setLoading(false) setLoading(false)
} }
}, [account, isDarkMode, theme.accent1]) }, [account, isDarkMode, swapUrl, theme.accent1])
useEffect(() => { useEffect(() => {
fetchSignedIframeUrl() fetchSignedIframeUrl()

@ -343,7 +343,7 @@ exports[`LoadedRow.tsx renders a row 1`] = `
> >
<a <a
class="c0" class="c0"
href="#/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" href="/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
> >
<div <div
class="c1" class="c1"

@ -12,8 +12,9 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from 'react-query' import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom' import { BrowserRouter, HashRouter } from 'react-router-dom'
import { SystemThemeUpdater } from 'theme/components/ThemeToggle' import { SystemThemeUpdater } from 'theme/components/ThemeToggle'
import { isBrowserRouterEnabled } from 'utils/env'
import Web3Provider from './components/Web3Provider' import Web3Provider from './components/Web3Provider'
import { LanguageProvider } from './i18n' import { LanguageProvider } from './i18n'
@ -51,12 +52,14 @@ const queryClient = new QueryClient()
const container = document.getElementById('root') as HTMLElement const container = document.getElementById('root') as HTMLElement
const Router = isBrowserRouterEnabled() ? BrowserRouter : HashRouter
createRoot(container).render( createRoot(container).render(
<StrictMode> <StrictMode>
<Provider store={store}> <Provider store={store}>
<FeatureFlagsProvider> <FeatureFlagsProvider>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<HashRouter> <Router>
<LanguageProvider> <LanguageProvider>
<Web3Provider> <Web3Provider>
<ApolloProvider client={apolloClient}> <ApolloProvider client={apolloClient}>
@ -70,7 +73,7 @@ createRoot(container).render(
</ApolloProvider> </ApolloProvider>
</Web3Provider> </Web3Provider>
</LanguageProvider> </LanguageProvider>
</HashRouter> </Router>
</QueryClientProvider> </QueryClientProvider>
</FeatureFlagsProvider> </FeatureFlagsProvider>
</Provider> </Provider>

@ -95,7 +95,7 @@ const ActionButton = ({
<StyledActionButton <StyledActionButton
selected={isSelected} selected={isSelected}
isDisabled={isDisabled} isDisabled={isDisabled}
onClick={(e: React.MouseEvent) => (isDisabled ? undefined : clickActionButton(e))} onClick={(e) => (isDisabled ? undefined : clickActionButton(e))}
> >
{children} {children}
</StyledActionButton> </StyledActionButton>

@ -38,6 +38,7 @@ export const eventRow = style([
borderBottomColor: 'surface3', borderBottomColor: 'surface3',
}), }),
{ {
textDecoration: 'none',
height: '84px', height: '84px',
':hover': { ':hover': {
background: themeVars.colors.surface1, background: themeVars.colors.surface1,

@ -3,11 +3,11 @@ import { NftActivityType } from 'graphql/data/__generated__/types-and-hooks'
import { useNftActivity } from 'graphql/data/nft/NftActivity' import { useNftActivity } from 'graphql/data/nft/NftActivity'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { useBag, useIsMobile } from 'nft/hooks' import { useBag, useIsMobile, useNativeUsdPrice } from 'nft/hooks'
import { useNativeUsdPrice } from 'nft/hooks/useUsdPrice' import { ActivityEventType } from 'nft/types'
import { ActivityEvent, ActivityEventType } from 'nft/types'
import { useCallback, useReducer } from 'react' import { useCallback, useReducer } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component' import InfiniteScroll from 'react-infinite-scroll-component'
import { Link } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import * as styles from './Activity.css' import * as styles from './Activity.css'
@ -60,8 +60,6 @@ export const reduceFilters = (state: typeof initialFilterState, action: { eventT
return { ...state, [action.eventType]: !state[action.eventType] } return { ...state, [action.eventType]: !state[action.eventType] }
} }
const baseHref = (event: ActivityEvent) => `/#/nfts/asset/${event.collectionAddress}/${event.tokenId}?origin=activity`
export const Activity = ({ contractAddress, rarityVerified, collectionName, chainId }: ActivityProps) => { export const Activity = ({ contractAddress, rarityVerified, collectionName, chainId }: ActivityProps) => {
const [activeFilters, filtersDispatch] = useReducer(reduceFilters, initialFilterState) const [activeFilters, filtersDispatch] = useReducer(reduceFilters, initialFilterState)
@ -126,9 +124,11 @@ export const Activity = ({ contractAddress, rarityVerified, collectionName, chai
(event, i) => (event, i) =>
event.eventType && ( event.eventType && (
<Box <Box
as="a" as={Link}
data-testid="nft-activity-row" data-testid="nft-activity-row"
href={baseHref(event)} // @ts-ignore Box component is not typed properly to typecheck
// custom components' props and will incorrectly report `to` as invalid
to={`/nfts/asset/${event.collectionAddress}/${event.tokenId}?origin=activity`}
className={styles.eventRow} className={styles.eventRow}
key={i} key={i}
> >

@ -308,7 +308,7 @@ exports[`NftCard renders correctly 1`] = `
> >
<a <a
class="c1" class="c1"
href="#/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/3318?origin=collection" href="/nfts/asset/0xed5af388653567af2f388e6224dc7c4b3241c544/3318?origin=collection"
> >
<div <div
class="c2" class="c2"

@ -79,17 +79,15 @@ export const getMarketplaceIcon = (marketplace: string, size: string | number =
export const generateTweetForAsset = (asset: GenieAsset): string => { export const generateTweetForAsset = (asset: GenieAsset): string => {
return `https://twitter.com/intent/tweet?text=Check%20out%20${ return `https://twitter.com/intent/tweet?text=Check%20out%20${
asset.name ? encodeURIComponent(asset.name) : `${asset.collectionName}%20%23${asset.tokenId}` asset.name ? encodeURIComponent(asset.name) : `${asset.collectionName}%20%23${asset.tokenId}`
}%20(${asset.collectionName})%20https://app.uniswap.org/%23/nfts/asset/${asset.address}/${ }%20(${asset.collectionName})%20https://app.uniswap.org/nfts/asset/${asset.address}/${asset.tokenId}%20via%20@uniswap`
asset.tokenId
}%20via%20@uniswap`
} }
export const generateTweetForPurchase = (assets: UpdatedGenieAsset[], txHashUrl: string): string => { export const generateTweetForPurchase = (assets: UpdatedGenieAsset[], txHashUrl: string): string => {
const multipleCollections = assets.length > 0 && assets.some((asset) => asset.address !== assets[0].address) const multipleCollections = assets.length > 0 && assets.some((asset) => asset.address !== assets[0].address)
const collectionUrl = assets.length > 0 && !multipleCollections ? `/collection/${assets[0].address}` : '' const collectionUrl = assets.length > 0 && !multipleCollections ? `collection/${assets[0].address}` : ''
const tweetText = `I just purchased ${ const tweetText = `I just purchased ${
multipleCollections ? `${assets.length} NFTs` : `${assets.length} ${assets[0].collectionName ?? 'NFT'}` multipleCollections ? `${assets.length} NFTs` : `${assets.length} ${assets[0].collectionName ?? 'NFT'}`
} with @Uniswap 🦄\n\nhttps://app.uniswap.org/#/nfts${collectionUrl}\n${txHashUrl}` } with @Uniswap 🦄\n\nhttps://app.uniswap.org/nfts/${collectionUrl}\n${txHashUrl}`
return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}` return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`
} }
@ -119,10 +117,10 @@ export const generateTweetForList = (assets: WalletAsset[]): string => {
: `${assets[0].collection?.name} ` ?? '' : `${assets[0].collection?.name} ` ?? ''
}${assets[0].name} for ${getMinListingPrice(assets[0].newListings ?? [])} ETH on ${assets[0].marketplaces }${assets[0].name} for ${getMinListingPrice(assets[0].newListings ?? [])} ETH on ${assets[0].marketplaces
?.map((market) => market.name) ?.map((market) => market.name)
.join(', ')}. Buy it on @Uniswap at https://app.uniswap.org/#${getAssetHref(assets[0])}` .join(', ')}. Buy it on @Uniswap at https://app.uniswap.org/${getAssetHref(assets[0])}`
: `I just listed ${ : `I just listed ${
assets.length assets.length
} items on @Uniswap at https://app.uniswap.org/#/nfts/profile\n\nCollections: ${mapAssetsToCollections(assets) } items on @Uniswap at https://app.uniswap.org/nfts/profile\n\nCollections: ${mapAssetsToCollections(assets)
.map(({ collection, items }) => `${collection} ${items.map((item) => item).join(', ')}`) .map(({ collection, items }) => `${collection} ${items.map((item) => item).join(', ')}`)
.join(', ')} \n\nMarketplaces: ${assets[0].marketplaces?.map((market) => market.name).join(', ')}` .join(', ')} \n\nMarketplaces: ${assets[0].marketplaces?.map((market) => market.name).join(', ')}`
return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}` return `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetText)}`

@ -17,7 +17,7 @@ import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { flexRowNoWrap } from 'theme/styles' import { flexRowNoWrap } from 'theme/styles'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { STATSIG_DUMMY_KEY } from 'tracing' import { STATSIG_DUMMY_KEY } from 'tracing'
import { getEnvName } from 'utils/env' import { getEnvName, isBrowserRouterEnabled } from 'utils/env'
import { getCurrentPageFromLocation } from 'utils/urlRoutes' import { getCurrentPageFromLocation } from 'utils/urlRoutes'
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals' import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
@ -112,7 +112,8 @@ export default function App() {
const isLoaded = useFeatureFlagsIsLoaded() const isLoaded = useFeatureFlagsIsLoaded()
const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
const { pathname } = useLocation() const browserRouterEnabled = isBrowserRouterEnabled()
const { hash, pathname } = useLocation()
const currentPage = getCurrentPageFromLocation(pathname) const currentPage = getCurrentPageFromLocation(pathname)
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const [routerPreference] = useRouterPreference() const [routerPreference] = useRouterPreference()
@ -211,7 +212,13 @@ export default function App() {
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
{isLoaded ? ( {isLoaded ? (
<Routes> <Routes>
<Route path="/" element={<Landing />} /> <Route
path="/"
element={
// If we match "/" and # is defined, we are using BrowserRouter and need to redirect.
browserRouterEnabled && hash ? <Navigate to={hash.replace('#', '')} replace /> : <Landing />
}
/>
<Route path="tokens" element={<Tokens />}> <Route path="tokens" element={<Tokens />}>
<Route path=":chainName" /> <Route path=":chainName" />

@ -1743,7 +1743,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
> >
<a <a
class="c2" class="c2"
href="#/swap" href="/swap"
> >
<div <div
class="c3" class="c3"
@ -2073,7 +2073,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
> >
<a <a
class="c2 c15 c58 c59" class="c2 c15 c58 c59"
href="#/swap" href="/swap"
> >
<p <p
class="c60" class="c60"
@ -2140,7 +2140,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
> >
<a <a
class="c66" class="c66"
href="#/swap" href="/swap"
> >
<div <div
class="c67" class="c67"
@ -2220,7 +2220,7 @@ exports[`disable nft on landing page does not render nft information and card 1`
</a> </a>
<a <a
class="c72" class="c72"
href="#/pools" href="/pools"
> >
<div <div
class="c67" class="c67"
@ -2405,19 +2405,19 @@ exports[`disable nft on landing page does not render nft information and card 1`
</span> </span>
<a <a
class="c96 c97" class="c96 c97"
href="#/swap" href="/swap"
> >
Swap Swap
</a> </a>
<a <a
class="c96 c97" class="c96 c97"
href="#/tokens" href="/tokens"
> >
Tokens Tokens
</a> </a>
<a <a
class="c96 c97" class="c96 c97"
href="#/pools" href="/pools"
> >
Pools Pools
</a> </a>
@ -4358,7 +4358,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
> >
<a <a
class="c2" class="c2"
href="#/swap" href="/swap"
> >
<div <div
class="c3" class="c3"
@ -4688,7 +4688,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
> >
<a <a
class="c2 c15 c58 c59" class="c2 c15 c58 c59"
href="#/swap" href="/swap"
> >
<p <p
class="c60" class="c60"
@ -4755,7 +4755,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
> >
<a <a
class="c66" class="c66"
href="#/swap" href="/swap"
> >
<div <div
class="c67" class="c67"
@ -4781,7 +4781,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
</a> </a>
<a <a
class="c71" class="c71"
href="#/nfts" href="/nfts"
> >
<div <div
class="c67" class="c67"
@ -4861,7 +4861,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
</a> </a>
<a <a
class="c73" class="c73"
href="#/pools" href="/pools"
> >
<div <div
class="c67" class="c67"
@ -5046,25 +5046,25 @@ exports[`disable nft on landing page renders nft information and card 1`] = `
</span> </span>
<a <a
class="c97 c98" class="c97 c98"
href="#/swap" href="/swap"
> >
Swap Swap
</a> </a>
<a <a
class="c97 c98" class="c97 c98"
href="#/tokens" href="/tokens"
> >
Tokens Tokens
</a> </a>
<a <a
class="c97 c98" class="c97 c98"
href="#/nfts" href="/nfts"
> >
NFTs NFTs
</a> </a>
<a <a
class="c97 c98" class="c97 c98"
href="#/pools" href="/pools"
> >
Pools Pools
</a> </a>

@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import type { TransactionResponse } from '@ethersproject/providers' import type { TransactionResponse } from '@ethersproject/providers'
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { InterfacePageName } from '@uniswap/analytics-events' import { InterfacePageName } from '@uniswap/analytics-events'
import { ChainId, Currency, CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core'
import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk' import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { Trace } from 'analytics' import { Trace } from 'analytics'
@ -29,12 +29,12 @@ import { useV3PositionFees } from 'hooks/useV3PositionFees'
import { useV3PositionFromTokenId } from 'hooks/useV3Positions' import { useV3PositionFromTokenId } from 'hooks/useV3Positions'
import { useSingleCallResult } from 'lib/hooks/multicall' import { useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useCallback, useMemo, useRef, useState } from 'react' import { PropsWithChildren, useCallback, useMemo, useRef, useState } from 'react'
import { Link, useParams } from 'react-router-dom' import { Link, useParams } from 'react-router-dom'
import { Bound } from 'state/mint/v3/actions' import { Bound } from 'state/mint/v3/actions'
import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks' import { useIsTransactionPending, useTransactionAdder } from 'state/transactions/hooks'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { ExternalLink, HideExtraSmall, HideSmall, ThemedText } from 'theme' import { ExternalLink, HideExtraSmall, HideSmall, StyledRouterLink, ThemedText } from 'theme'
import { currencyId } from 'utils/currencyId' import { currencyId } from 'utils/currencyId'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { formatPrice, NumberType } from 'utils/formatNumbers' import { formatPrice, NumberType } from 'utils/formatNumbers'
@ -52,15 +52,6 @@ import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { LoadingRows } from './styled' import { LoadingRows } from './styled'
const getTokenLink = (chainId: ChainId, address: string) => {
if (isGqlSupportedChain(chainId)) {
const chainName = CHAIN_IDS_TO_NAMES[chainId]
return `${window.location.origin}/#/tokens/${chainName}/${address}`
} else {
return getExplorerLink(chainId, address, ExplorerDataType.TOKEN)
}
}
const PositionPageButtonPrimary = styled(ButtonPrimary)` const PositionPageButtonPrimary = styled(ButtonPrimary)`
width: 228px; width: 228px;
height: 40px; height: 40px;
@ -211,17 +202,31 @@ function CurrentPriceCard({
) )
} }
const TokenLink = ({
children,
chainId,
address,
}: PropsWithChildren<{ chainId: keyof typeof CHAIN_IDS_TO_NAMES; address: string }>) => {
const chainName = CHAIN_IDS_TO_NAMES[chainId]
return <StyledRouterLink to={`/tokens/${chainName}/${address}`}>{children}</StyledRouterLink>
}
const ExternalTokenLink = ({ children, chainId, address }: PropsWithChildren<{ chainId: number; address: string }>) => {
return <ExternalLink href={getExplorerLink(chainId, address, ExplorerDataType.TOKEN)}>{children}</ExternalLink>
}
function LinkedCurrency({ chainId, currency }: { chainId?: number; currency?: Currency }) { function LinkedCurrency({ chainId, currency }: { chainId?: number; currency?: Currency }) {
const address = (currency as Token)?.address const address = (currency as Token)?.address
if (typeof chainId === 'number' && address) { if (typeof chainId === 'number' && address) {
const Link = isGqlSupportedChain(chainId) ? TokenLink : ExternalTokenLink
return ( return (
<ExternalLink href={getTokenLink(chainId, address)}> <Link chainId={chainId} address={address}>
<RowFixed> <RowFixed>
<CurrencyLogo currency={currency} size="20px" style={{ marginRight: '0.5rem' }} /> <CurrencyLogo currency={currency} size="20px" style={{ marginRight: '0.5rem' }} />
<ThemedText.DeprecatedMain>{currency?.symbol} </ThemedText.DeprecatedMain> <ThemedText.DeprecatedMain>{currency?.symbol} </ThemedText.DeprecatedMain>
</RowFixed> </RowFixed>
</ExternalLink> </Link>
) )
} }

@ -17,15 +17,15 @@ describe('document', () => {
[{ request: { mode: 'navigate' }, url: { hostname: 'example.com', pathname: '' } }, false], [{ request: { mode: 'navigate' }, url: { hostname: 'example.com', pathname: '' } }, false],
[{ request: {}, url: { hostname: 'app.uniswap.org', pathname: '' } }, false], [{ request: {}, url: { hostname: 'app.uniswap.org', pathname: '' } }, false],
[{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap.org', pathname: '' } }, true], [{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap.org', pathname: '' } }, true],
[{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap.org', pathname: '/#/swap' } }, true], [{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap.org', pathname: '/swap' } }, true],
[{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap.org', pathname: '/asset.gif' } }, false], [{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap.org', pathname: '/asset.gif' } }, false],
[{ request: {}, url: { hostname: 'app.uniswap-staging.org', pathname: '' } }, false], [{ request: {}, url: { hostname: 'app.corn-staging.com', pathname: '' } }, false],
[{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap-staging.org', pathname: '' } }, true], [{ request: { mode: 'navigate' }, url: { hostname: 'app.corn-staging.com', pathname: '' } }, true],
[{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap-staging.org', pathname: '/#/swap' } }, true], [{ request: { mode: 'navigate' }, url: { hostname: 'app.corn-staging.com', pathname: '/swap' } }, true],
[{ request: { mode: 'navigate' }, url: { hostname: 'app.uniswap-staging.org', pathname: '/asset.gif' } }, false], [{ request: { mode: 'navigate' }, url: { hostname: 'app.corn-staging.com', pathname: '/asset.gif' } }, false],
[{ request: {}, url: { hostname: 'localhost', pathname: '' } }, false], [{ request: {}, url: { hostname: 'localhost', pathname: '' } }, false],
[{ request: { mode: 'navigate' }, url: { hostname: 'localhost', pathname: '' } }, true], [{ request: { mode: 'navigate' }, url: { hostname: 'localhost', pathname: '' } }, true],
[{ request: { mode: 'navigate' }, url: { hostname: 'localhost', pathname: '/#/swap' } }, true], [{ request: { mode: 'navigate' }, url: { hostname: 'localhost', pathname: '/swap' } }, true],
[{ request: { mode: 'navigate' }, url: { hostname: 'localhost', pathname: '/asset.gif' } }, false], [{ request: { mode: 'navigate' }, url: { hostname: 'localhost', pathname: '/asset.gif' } }, false],
] as [RouteMatchCallbackOptions, boolean][] ] as [RouteMatchCallbackOptions, boolean][]

@ -10,7 +10,7 @@ import { en } from 'make-plural/plurals'
import { ReactElement, ReactNode } from 'react' import { ReactElement, ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from 'react-query' import { QueryClient, QueryClientProvider } from 'react-query'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import store from 'state' import store from 'state'
import ThemeProvider from 'theme' import ThemeProvider from 'theme'
@ -30,7 +30,7 @@ const WithProviders = ({ children }: { children?: ReactNode }) => {
<MockedI18nProvider> <MockedI18nProvider>
<Provider store={store}> <Provider store={store}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<HashRouter> <BrowserRouter>
{/* {/*
* Web3Provider is mocked through setupTests.ts * Web3Provider is mocked through setupTests.ts
* To test behavior that depends on Web3Provider, use jest.unmock('@web3-react/core') * To test behavior that depends on Web3Provider, use jest.unmock('@web3-react/core')
@ -40,7 +40,7 @@ const WithProviders = ({ children }: { children?: ReactNode }) => {
<ThemeProvider>{children}</ThemeProvider> <ThemeProvider>{children}</ThemeProvider>
</BlockNumberProvider> </BlockNumberProvider>
</MockedProvider> </MockedProvider>
</HashRouter> </BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
</Provider> </Provider>
</MockedI18nProvider> </MockedI18nProvider>

@ -103,7 +103,7 @@ describe('trace', () => {
const errorEvent: ErrorEvent = { const errorEvent: ErrorEvent = {
type: undefined, type: undefined,
request: { request: {
url: 'https://app.uniswap.org/#/pools', url: 'https://app.uniswap.org/pools',
}, },
} }
const eventHint: EventHint = {} const eventHint: EventHint = {}
@ -114,7 +114,7 @@ describe('trace', () => {
const errorEvent: ErrorEvent = { const errorEvent: ErrorEvent = {
type: undefined, type: undefined,
request: { request: {
url: 'https://app.uniswap.org/#', url: 'https://app.uniswap.org/',
}, },
} }
const eventHint: EventHint = {} const eventHint: EventHint = {}

@ -20,7 +20,25 @@ export function isAppUniswapOrg({ hostname }: { hostname: string }): boolean {
} }
export function isAppUniswapStagingOrg({ hostname }: { hostname: string }): boolean { export function isAppUniswapStagingOrg({ hostname }: { hostname: string }): boolean {
return hostname === 'app.uniswap-staging.org' return hostname === 'app.corn-staging.com'
}
export function isBrowserRouterEnabled(): boolean {
if (isProductionEnv()) {
if (
isAppUniswapOrg(window.location) ||
isAppUniswapStagingOrg(window.location) ||
isLocalhost(window.location) // cypress tests
) {
return true
}
return false // production builds *not* served through our domains or localhost, eg IPFS
}
return true // local dev builds
}
function isLocalhost({ hostname }: { hostname: string }): boolean {
return hostname === 'localhost'
} }
export function isSentryEnabled(): boolean { export function isSentryEnabled(): boolean {

@ -5625,11 +5625,6 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/history@*", "@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1": "@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@ -5824,23 +5819,6 @@
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.0"
redux "^4.0.0" redux "^4.0.0"
"@types/react-router-dom@^5.3.3":
version "5.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
dependencies:
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.16"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.16.tgz#f3ba045fb96634e38b21531c482f9aeb37608a99"
integrity sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-table@^7.7.12": "@types/react-table@^7.7.12":
version "7.7.12" version "7.7.12"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.12.tgz#628011d3cb695b07c678704a61f2f1d5b8e567fd" resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.12.tgz#628011d3cb695b07c678704a61f2f1d5b8e567fd"