feat: add sitemap for app.uniswap.org (#7408)

* feat: add sitemap for app.uniswap.org

* feat: script to update lastmod

* fix: deps and snapshots

* fix: use xml2js

* fix: improve test and sitemap
This commit is contained in:
eddie 2023-10-05 12:19:58 -07:00 committed by GitHub
parent bab8506919
commit 3ced65b8a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 518 additions and 161 deletions

@ -13,10 +13,11 @@
"graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts", "graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts",
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph", "graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
"graphql": "yarn graphql:fetch && yarn graphql:generate", "graphql": "yarn graphql:fetch && yarn graphql:generate",
"sitemap:generate": "node scripts/generate-sitemap.js",
"i18n:extract": "lingui extract --locale en-US", "i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "lingui compile", "i18n:compile": "lingui compile",
"i18n": "yarn i18n:extract --clean && yarn i18n:compile", "i18n": "yarn i18n:extract --clean && yarn i18n:compile",
"prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"", "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\" \"npm:sitemap:generate\"",
"start": "craco start", "start": "craco 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", "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",
@ -114,6 +115,7 @@
"@types/ua-parser-js": "^0.7.36", "@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@types/wcag-contrast": "^3.0.0", "@types/wcag-contrast": "^3.0.0",
"@types/xml2js": "^0.4.12",
"@uniswap/default-token-list": "^11.2.0", "@uniswap/default-token-list": "^11.2.0",
"@uniswap/eslint-config": "^1.2.0", "@uniswap/eslint-config": "^1.2.0",
"@vanilla-extract/jest-transform": "^1.1.1", "@vanilla-extract/jest-transform": "^1.1.1",
@ -293,6 +295,7 @@
"workbox-navigation-preload": "^6.1.0", "workbox-navigation-preload": "^6.1.0",
"workbox-precaching": "^6.1.0", "workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0", "workbox-routing": "^6.1.0",
"xml2js": "^0.6.2",
"zustand": "^4.3.6" "zustand": "^4.3.6"
}, },
"engines": { "engines": {

19
public/sitemap.xml Normal file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url loc="https://app.uniswap.org/" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="1"/>
<url loc="https://app.uniswap.org/tokens" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.8"/>
<url loc="https://app.uniswap.org/send" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/swap" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.9"/>
<url loc="https://app.uniswap.org/pool/v2/find" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/pool/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/pool" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/pools/v2/find" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/pools/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/pools" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.7"/>
<url loc="https://app.uniswap.org/add/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/add" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/increase" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/migrate/v2" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/nfts" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
<url loc="https://app.uniswap.org/nfts/profile" lastmod="2023-10-05T17:48:32.538Z" changefreq="weekly" priority="0.6"/>
</urlset>

@ -0,0 +1,25 @@
/* eslint-env node */
const fs = require('fs')
const { parseStringPromise, Builder } = require('xml2js')
fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
try {
const sitemap = await parseStringPromise(data)
const lastmodDate = new Date().toISOString()
if (sitemap.urlset.url) {
sitemap.urlset.url.forEach((url) => {
url['$'].lastmod = lastmodDate
})
}
const builder = new Builder()
const xml = builder.buildObject(sitemap)
fs.writeFile('./public/sitemap.xml', xml, (error) => {
if (error) throw error
console.log('Sitemap updated')
})
} catch {
throw new Error('Error parsing sitemap.xml')
}
})

@ -5,51 +5,27 @@ import ErrorBoundary from 'components/ErrorBoundary'
import Loader from 'components/Icons/LoadingSpinner' import Loader from 'components/Icons/LoadingSpinner'
import NavBar, { PageTabs } from 'components/NavBar' import NavBar, { PageTabs } from 'components/NavBar'
import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useFeatureFlagsIsLoaded } from 'featureFlags'
import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { useBag } from 'nft/hooks/useBag' import { useBag } from 'nft/hooks/useBag'
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { useRouterPreference } from 'state/user/hooks' import { useRouterPreference } from 'state/user/hooks'
import { StatsigProvider, StatsigUser } from 'statsig-react' import { StatsigProvider, StatsigUser } from 'statsig-react'
import styled from 'styled-components' import styled from 'styled-components'
import { SpinnerSVG } from 'theme/components'
import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader' import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader'
import { useIsDarkMode } from 'theme/components/ThemeToggle' 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, isBrowserRouterEnabled } from 'utils/env' import { getEnvName } from 'utils/env'
import { getDownloadAppLink } from 'utils/openDownloadApp' import { getDownloadAppLink } from 'utils/openDownloadApp'
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'
// High-traffic pages (index and /swap) should not be lazy-loaded. import { RouteDefinition, routes, useRouterConfig } from './RouteDefinitions'
import Landing from './Landing'
import Swap from './Swap'
const AppChrome = lazy(() => import('./AppChrome')) const AppChrome = lazy(() => import('./AppChrome'))
const NftExplore = lazy(() => import('nft/pages/explore'))
const Collection = lazy(() => import('nft/pages/collection'))
const Profile = lazy(() => import('nft/pages/profile'))
const Asset = lazy(() => import('nft/pages/asset/Asset'))
const AddLiquidity = lazy(() => import('pages/AddLiquidity'))
const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects'))
const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects'))
const MigrateV2 = lazy(() => import('pages/MigrateV2'))
const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair'))
const NotFound = lazy(() => import('pages/NotFound'))
const Pool = lazy(() => import('pages/Pool'))
const PositionPage = lazy(() => import('pages/Pool/PositionPage'))
const PoolV2 = lazy(() => import('pages/Pool/v2'))
const PoolDetails = lazy(() => import('pages/PoolDetails'))
const PoolFinder = lazy(() => import('pages/PoolFinder'))
const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity'))
const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3'))
const TokenDetails = lazy(() => import('pages/TokenDetails'))
const Tokens = lazy(() => import('pages/Tokens'))
const Vote = lazy(() => import('pages/Vote'))
const BodyWrapper = styled.div` const BodyWrapper = styled.div`
display: flex; display: flex;
@ -93,32 +69,18 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>`
z-index: ${Z_INDEX.dropdown}; z-index: ${Z_INDEX.dropdown};
` `
// this is the same svg defined in assets/images/blue-loader.svg
// it is defined here because the remote asset may not have had time to load when this file is executing
const LazyLoadSpinner = () => (
<SpinnerSVG width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M92 47C92 22.1472 71.8528 2 47 2C22.1472 2 2 22.1472 2 47C2 71.8528 22.1472 92 47 92"
stroke="#2172E5"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</SpinnerSVG>
)
export default function App() { export default function App() {
const isLoaded = useFeatureFlagsIsLoaded() const isLoaded = useFeatureFlagsIsLoaded()
const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) const [, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
const browserRouterEnabled = isBrowserRouterEnabled()
const location = useLocation() const location = useLocation()
const { hash, pathname } = location const { pathname } = location
const currentPage = getCurrentPageFromLocation(pathname) const currentPage = getCurrentPageFromLocation(pathname)
const isDarkMode = useIsDarkMode() const isDarkMode = useIsDarkMode()
const [routerPreference] = useRouterPreference() const [routerPreference] = useRouterPreference()
const [scrolledState, setScrolledState] = useState(false) const [scrolledState, setScrolledState] = useState(false)
const infoPoolPageEnabled = useInfoPoolPageEnabled()
const routerConfig = useRouterConfig()
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0) window.scrollTo(0, 0)
@ -224,116 +186,15 @@ export default function App() {
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
{isLoaded ? ( {isLoaded ? (
<Routes> <Routes>
<Route {routes.map((route: RouteDefinition) =>
path="/" route.enabled(routerConfig) ? (
element={ <Route key={route.path} path={route.path} element={route.getElement(routerConfig)}>
// If we match "/" and # is defined, we are using BrowserRouter and need to redirect. {route.nestedPaths.map((nestedPath) => (
browserRouterEnabled && hash ? <Navigate to={hash.replace('#', '')} replace /> : <Landing /> <Route path={nestedPath} key={`${route.path}/${nestedPath}`} />
} ))}
/> </Route>
) : null
<Route path="tokens" element={<Tokens />}>
<Route path=":chainName" />
</Route>
<Route path="tokens/:chainName/:tokenAddress" element={<TokenDetails />} />
{infoPoolPageEnabled && <Route path="pools/:chainName/:poolAddress" element={<PoolDetails />} />}
<Route
path="vote/*"
element={
<Suspense fallback={<LazyLoadSpinner />}>
<Vote />
</Suspense>
}
/>
<Route path="create-proposal" element={<Navigate to="/vote/create-proposal" replace />} />
<Route path="send" element={<Navigate to={{ ...location, pathname: '/swap' }} replace />} />
<Route path="swap" element={<Swap />} />
<Route path="pool/v2/find" element={<PoolFinder />} />
<Route path="pool/v2" element={<PoolV2 />} />
<Route path="pool" element={<Pool />} />
<Route path="pool/:tokenId" element={<PositionPage />} />
<Route path="pools/v2/find" element={<PoolFinder />} />
<Route path="pools/v2" element={<PoolV2 />} />
<Route path="pools" element={<Pool />} />
<Route path="pools/:tokenId" element={<PositionPage />} />
<Route path="add/v2" element={<RedirectDuplicateTokenIdsV2 />}>
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
</Route>
<Route path="add" element={<RedirectDuplicateTokenIds />}>
{/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
</Route>
<Route path="increase" element={<AddLiquidity />}>
<Route path=":currencyIdA" />
<Route path=":currencyIdA/:currencyIdB" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount" />
<Route path=":currencyIdA/:currencyIdB/:feeAmount/:tokenId" />
</Route>
<Route path="remove/v2/:currencyIdA/:currencyIdB" element={<RemoveLiquidity />} />
<Route path="remove/:tokenId" element={<RemoveLiquidityV3 />} />
<Route path="migrate/v2" element={<MigrateV2 />} />
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
{!shouldDisableNFTRoutes && (
<>
<Route
path="/nfts"
element={
<Suspense fallback={null}>
<NftExplore />
</Suspense>
}
/>
<Route
path="/nfts/asset/:contractAddress/:tokenId"
element={
<Suspense fallback={null}>
<Asset />
</Suspense>
}
/>
<Route
path="/nfts/profile"
element={
<Suspense fallback={null}>
<Profile />
</Suspense>
}
/>
<Route
path="/nfts/collection/:contractAddress"
element={
<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="/not-found" element={<NotFound />} />
</Routes> </Routes>
) : ( ) : (
<Loader /> <Loader />

@ -0,0 +1,208 @@
import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
import { useAtom } from 'jotai'
import { lazy, ReactNode, Suspense, useMemo } from 'react'
import { Navigate, useLocation } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { SpinnerSVG } from 'theme/components'
import { isBrowserRouterEnabled } from 'utils/env'
// High-traffic pages (index and /swap) should not be lazy-loaded.
import Landing from './Landing'
import Swap from './Swap'
const NftExplore = lazy(() => import('nft/pages/explore'))
const Collection = lazy(() => import('nft/pages/collection'))
const Profile = lazy(() => import('nft/pages/profile'))
const Asset = lazy(() => import('nft/pages/asset/Asset'))
const AddLiquidity = lazy(() => import('pages/AddLiquidity'))
const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects'))
const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects'))
const MigrateV2 = lazy(() => import('pages/MigrateV2'))
const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair'))
const NotFound = lazy(() => import('pages/NotFound'))
const Pool = lazy(() => import('pages/Pool'))
const PositionPage = lazy(() => import('pages/Pool/PositionPage'))
const PoolV2 = lazy(() => import('pages/Pool/v2'))
const PoolDetails = lazy(() => import('pages/PoolDetails'))
const PoolFinder = lazy(() => import('pages/PoolFinder'))
const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity'))
const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3'))
const TokenDetails = lazy(() => import('pages/TokenDetails'))
const Tokens = lazy(() => import('pages/Tokens'))
const Vote = lazy(() => import('pages/Vote'))
// this is the same svg defined in assets/images/blue-loader.svg
// it is defined here because the remote asset may not have had time to load when this file is executing
const LazyLoadSpinner = () => (
<SpinnerSVG width="94" height="94" viewBox="0 0 94 94" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M92 47C92 22.1472 71.8528 2 47 2C22.1472 2 2 22.1472 2 47C2 71.8528 22.1472 92 47 92"
stroke="#2172E5"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</SpinnerSVG>
)
interface RouterConfig {
browserRouterEnabled?: boolean
hash?: string
infoPoolPageEnabled?: boolean
shouldDisableNFTRoutes?: boolean
}
/**
* Convenience hook which organizes the router configuration into a single object.
*/
export function useRouterConfig(): RouterConfig {
const browserRouterEnabled = isBrowserRouterEnabled()
const { hash } = useLocation()
const infoPoolPageEnabled = useInfoPoolPageEnabled()
const [shouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
return useMemo(
() => ({
browserRouterEnabled,
hash,
infoPoolPageEnabled,
shouldDisableNFTRoutes: Boolean(shouldDisableNFTRoutes),
}),
[browserRouterEnabled, hash, infoPoolPageEnabled, shouldDisableNFTRoutes]
)
}
export interface RouteDefinition {
path: string
nestedPaths: string[]
enabled: (args: RouterConfig) => boolean
getElement: (args: RouterConfig) => ReactNode
}
// Assigns the defaults to the route definition.
function createRouteDefinition(route: Partial<RouteDefinition>): RouteDefinition {
return {
getElement: () => null,
enabled: () => true,
path: '/',
nestedPaths: [],
// overwrite the defaults
...route,
}
}
export const routes: RouteDefinition[] = [
createRouteDefinition({
path: '/',
getElement: (args) => {
return args.browserRouterEnabled && args.hash ? <Navigate to={args.hash.replace('#', '')} replace /> : <Landing />
},
}),
createRouteDefinition({
path: '/tokens',
nestedPaths: [':chainName'],
getElement: () => <Tokens />,
}),
createRouteDefinition({ path: '/tokens/:chainName/:tokenAddress', getElement: () => <TokenDetails /> }),
createRouteDefinition({
path: '/pools/:chainName/:poolAddress',
getElement: () => <PoolDetails />,
enabled: (args) => Boolean(args.infoPoolPageEnabled),
}),
createRouteDefinition({
path: '/vote/*',
getElement: () => (
<Suspense fallback={<LazyLoadSpinner />}>
<Vote />
</Suspense>
),
}),
createRouteDefinition({
path: '/create-proposal',
getElement: () => <Navigate to="/vote/create-proposal" replace />,
}),
createRouteDefinition({
path: '/send',
getElement: () => <Navigate to={{ ...location, pathname: '/swap' }} replace />,
}),
createRouteDefinition({ path: '/swap', getElement: () => <Swap /> }),
createRouteDefinition({ path: '/pool/v2/find', getElement: () => <PoolFinder /> }),
createRouteDefinition({ path: '/pool/v2', getElement: () => <PoolV2 /> }),
createRouteDefinition({ path: '/pool', getElement: () => <Pool /> }),
createRouteDefinition({ path: '/pool/:tokenId', getElement: () => <PositionPage /> }),
createRouteDefinition({ path: '/pools/v2/find', getElement: () => <PoolFinder /> }),
createRouteDefinition({ path: '/pools/v2', getElement: () => <PoolV2 /> }),
createRouteDefinition({ path: '/pools', getElement: () => <Pool /> }),
createRouteDefinition({ path: '/pools/:tokenId', getElement: () => <PositionPage /> }),
createRouteDefinition({
path: '/add/v2',
nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB'],
getElement: () => <RedirectDuplicateTokenIdsV2 />,
}),
createRouteDefinition({
path: '/add',
nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB', ':currencyIdA/:currencyIdB/:feeAmount'],
getElement: () => <RedirectDuplicateTokenIds />,
}),
createRouteDefinition({
path: '/increase',
nestedPaths: [
':currencyIdA',
':currencyIdA/:currencyIdB',
':currencyIdA/:currencyIdB/:feeAmount',
':currencyIdA/:currencyIdB/:feeAmount/:tokenId',
],
getElement: () => <AddLiquidity />,
}),
createRouteDefinition({ path: '/remove/v2/:currencyIdA/:currencyIdB', getElement: () => <RemoveLiquidity /> }),
createRouteDefinition({ path: '/remove/:tokenId', getElement: () => <RemoveLiquidityV3 /> }),
createRouteDefinition({ path: '/migrate/v2', getElement: () => <MigrateV2 /> }),
createRouteDefinition({ path: '/migrate/v2/:address', getElement: () => <MigrateV2Pair /> }),
createRouteDefinition({
path: '/nfts',
getElement: () => (
<Suspense fallback={null}>
<NftExplore />
</Suspense>
),
enabled: (args) => !args.shouldDisableNFTRoutes,
}),
createRouteDefinition({
path: '/nfts/asset/:contractAddress/:tokenId',
getElement: () => (
<Suspense fallback={null}>
<Asset />
</Suspense>
),
enabled: (args) => !args.shouldDisableNFTRoutes,
}),
createRouteDefinition({
path: '/nfts/profile',
getElement: () => (
<Suspense fallback={null}>
<Profile />
</Suspense>
),
enabled: (args) => !args.shouldDisableNFTRoutes,
}),
createRouteDefinition({
path: '/nfts/collection/:contractAddress',
getElement: () => (
<Suspense fallback={null}>
<Collection />
</Suspense>
),
enabled: (args) => !args.shouldDisableNFTRoutes,
}),
createRouteDefinition({
path: '/nfts/collection/:contractAddress/activity',
getElement: () => (
<Suspense fallback={null}>
<Collection />
</Suspense>
),
enabled: (args) => !args.shouldDisableNFTRoutes,
}),
createRouteDefinition({ path: '*', getElement: () => <Navigate to="/not-found" replace /> }),
createRouteDefinition({ path: '/not-found', getElement: () => <NotFound /> }),
]

@ -0,0 +1,200 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Routes router definition should match snapshot 1`] = `
Array [
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [
":chainName",
],
"path": "/tokens",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/tokens/:chainName/:tokenAddress",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pools/:chainName/:poolAddress",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/vote/*",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/create-proposal",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/send",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/swap",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pool/v2/find",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pool/v2",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pool",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pool/:tokenId",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pools/v2/find",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pools/v2",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pools",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/pools/:tokenId",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [
":currencyIdA",
":currencyIdA/:currencyIdB",
],
"path": "/add/v2",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [
":currencyIdA",
":currencyIdA/:currencyIdB",
":currencyIdA/:currencyIdB/:feeAmount",
],
"path": "/add",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [
":currencyIdA",
":currencyIdA/:currencyIdB",
":currencyIdA/:currencyIdB/:feeAmount",
":currencyIdA/:currencyIdB/:feeAmount/:tokenId",
],
"path": "/increase",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/remove/v2/:currencyIdA/:currencyIdB",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/remove/:tokenId",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/migrate/v2",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/migrate/v2/:address",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/nfts",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/nfts/asset/:contractAddress/:tokenId",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/nfts/profile",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/nfts/collection/:contractAddress",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/nfts/collection/:contractAddress/activity",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "*",
},
Object {
"enabled": [Function],
"getElement": [Function],
"nestedPaths": Array [],
"path": "/not-found",
},
]
`;

26
src/pages/routes.test.ts Normal file

@ -0,0 +1,26 @@
import fs from 'fs'
import { parseStringPromise } from 'xml2js'
import { routes } from './RouteDefinitions'
describe('Routes', () => {
it('sitemap URLs should exist as Router paths', async () => {
const pathNames: string[] = routes.map((routeDef) => routeDef.path)
const contents = fs.readFileSync('./public/sitemap.xml', 'utf8')
const sitemap = await parseStringPromise(contents)
const sitemapPaths = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname)
sitemapPaths.forEach((path: string) => {
expect(pathNames).toContain(path)
})
})
/**
* If you are updating the app routes, consider if you need to make a
* corresponding update to the sitemap.xml file.
*/
it('router definition should match snapshot', () => {
expect(routes).toMatchSnapshot()
})
})

@ -5921,6 +5921,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/xml2js@^0.4.12":
version "0.4.12"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.12.tgz#d9aae03295476fd5cbc74e0b572816208dbec6d1"
integrity sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==
dependencies:
"@types/node" "*"
"@types/yargs-parser@*": "@types/yargs-parser@*":
version "20.2.1" version "20.2.1"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
@ -15542,9 +15549,9 @@ mz@^2.7.0:
thenify-all "^1.0.0" thenify-all "^1.0.0"
nan@^2.14.0: nan@^2.14.0:
version "2.14.2" version "2.18.0"
resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz" resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==
nano-time@1.0.0: nano-time@1.0.0:
version "1.0.0" version "1.0.0"
@ -17227,9 +17234,9 @@ punycode@1.3.2, punycode@^1.3.2:
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@^2.1.0, punycode@^2.1.1: punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1" version "2.3.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
pure-rand@^6.0.0: pure-rand@^6.0.0:
version "6.0.2" version "6.0.2"
@ -21101,6 +21108,14 @@ xml2js@^0.4.5:
sax ">=0.6.0" sax ">=0.6.0"
xmlbuilder "~11.0.0" xmlbuilder "~11.0.0"
xml2js@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@~11.0.0: xmlbuilder@~11.0.0:
version "11.0.1" version "11.0.1"
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"