From c3849a862380cf566402d9eddd92b39cc95eff43 Mon Sep 17 00:00:00 2001 From: Caleb Kniffen Date: Mon, 21 Aug 2023 18:44:15 -0500 Subject: [PATCH] fix: search in custom mode (#806) Custom mode searching did not work after the react-router 6 updates. --- src/containers/App/navigation.ts | 29 ++++++ src/containers/App/routes.ts | 30 +----- src/containers/Header/Search.tsx | 120 ++++++++++++++-------- src/containers/Header/index.tsx | 2 +- src/containers/Header/test/Search.test.js | 2 + 5 files changed, 112 insertions(+), 71 deletions(-) create mode 100644 src/containers/App/navigation.ts diff --git a/src/containers/App/navigation.ts b/src/containers/App/navigation.ts new file mode 100644 index 000000000..1ab8f8586 --- /dev/null +++ b/src/containers/App/navigation.ts @@ -0,0 +1,29 @@ +import { buildPath } from '../shared/routing' +import { NavigationMenuAnyRoute } from '../Header/NavigationMenu' +import { LEDGERS_ROUTE, NETWORK_ROUTE, VALIDATOR_ROUTE } from './routes' + +const isNetwork = (path) => + path.indexOf(buildPath(NETWORK_ROUTE, {})) === 0 || + path.indexOf(buildPath(VALIDATOR_ROUTE, { identifier: '' })) === 0 + +// NOTE: for submenus, remove `path` field and add `children` array of objects +export const navigationConfig: NavigationMenuAnyRoute[] = [ + { + route: LEDGERS_ROUTE, + title: 'explorer', + current: (path: string) => !isNetwork(path), + }, + { + route: NETWORK_ROUTE, + title: 'network', + current: (path: string) => isNetwork(path), + }, + { + link: 'https://xrpl.org', + title: 'xrpl_org', + }, + { + link: 'https://github.com/ripple/explorer', + title: 'github', + }, +] diff --git a/src/containers/App/routes.ts b/src/containers/App/routes.ts index c4a0247a2..71c5419f0 100644 --- a/src/containers/App/routes.ts +++ b/src/containers/App/routes.ts @@ -1,6 +1,4 @@ -import { buildPath, RouteDefinition } from '../shared/routing' - -import { NavigationMenuAnyRoute } from '../Header/NavigationMenu' +import { RouteDefinition } from '../shared/routing' export const ACCOUNT_ROUTE: RouteDefinition<{ id?: string @@ -58,29 +56,3 @@ export const VALIDATOR_ROUTE: RouteDefinition<{ }> = { path: `/validators/:identifier/:tab?`, } - -const isNetwork = (path) => - path.indexOf(buildPath(NETWORK_ROUTE, {})) === 0 || - path.indexOf(buildPath(VALIDATOR_ROUTE, { identifier: '' })) === 0 - -// NOTE: for submenus, remove `path` field and add `children` array of objects -export const navigationConfig: NavigationMenuAnyRoute[] = [ - { - route: LEDGERS_ROUTE, - title: 'explorer', - current: (path: string) => !isNetwork(path), - }, - { - route: NETWORK_ROUTE, - title: 'network', - current: (path: string) => isNetwork(path), - }, - { - link: 'https://xrpl.org', - title: 'xrpl_org', - }, - { - link: 'https://github.com/ripple/explorer', - title: 'github', - }, -] diff --git a/src/containers/Header/Search.tsx b/src/containers/Header/Search.tsx index 0380efe99..0abc75bfd 100644 --- a/src/containers/Header/Search.tsx +++ b/src/containers/Header/Search.tsx @@ -20,6 +20,16 @@ import { import './search.scss' import { isValidPayString } from '../../rippled/payString' import { getTransaction } from '../../rippled/lib/rippled' +import { buildPath } from '../shared/routing' +import { + ACCOUNT_ROUTE, + LEDGER_ROUTE, + NFT_ROUTE, + PAYSTRING_ROUTE, + TOKEN_ROUTE, + TRANSACTION_ROUTE, + VALIDATOR_ROUTE, +} from '../App/routes' const determineHashType = async (id: string, rippledContext: XrplClient) => { try { @@ -32,66 +42,98 @@ const determineHashType = async (id: string, rippledContext: XrplClient) => { // separator for currency formats const separators = /[.:+-]/ -const getIdType = async (id: string, rippledContext: XrplClient) => { +const getRoute = async ( + id: string, + rippledContext: XrplClient, +): Promise<{ type: string; path: string } | null> => { if (DECIMAL_REGEX.test(id)) { - return 'ledgers' + return { + type: 'ledgers', + path: buildPath(LEDGER_ROUTE, { identifier: id }), + } } if (isValidClassicAddress(id)) { - return 'accounts' + return { + type: 'accounts', + path: buildPath(ACCOUNT_ROUTE, { id: normalizeAccount(id) }), + } } if (HASH_REGEX.test(id)) { // Transactions and NFTs share the same syntax // We must make an api call to ensure if it's one or the other - return determineHashType(id, rippledContext) + const type = await determineHashType(id, rippledContext) + let path + if (type === 'transactions') { + path = buildPath(TRANSACTION_ROUTE, { identifier: id.toUpperCase() }) + } else if (type === 'nft') { + path = buildPath(NFT_ROUTE, { id: id.toUpperCase() }) + } + + return { + path, + type, + } } if (isValidXAddress(id) || isValidClassicAddress(id.split(':')[0])) { - return 'accounts' // TODO: Consider a new path/page specific to X-addresses + return { + type: 'accounts', + path: buildPath(ACCOUNT_ROUTE, { id: normalizeAccount(id) }), // TODO: Consider a new path/page specific to X-addresses + } } if (isValidPayString(id) || isValidPayString(id.replace('@', '$'))) { - return 'paystrings' + let normalizedId = id + if (!isValidPayString(id)) { + normalizedId = id.replace('@', '$') + } + + return { + type: 'paystrings', + path: buildPath(PAYSTRING_ROUTE, { id: normalizedId }), + } } if ( (CURRENCY_REGEX.test(id) || FULL_CURRENCY_REGEX.test(id)) && isValidClassicAddress(id.split(separators)[1]) ) { - return 'token' + const components = id.split(separators) + return { + type: 'token', + path: buildPath(TOKEN_ROUTE, { + token: `${components[0].toLowerCase()}.${components[1]}`, + }), + } } if (VALIDATORS_REGEX.test(id)) { - return 'validators' + return { + type: 'validators', + path: buildPath(VALIDATOR_ROUTE, { identifier: normalizeAccount(id) }), + } } - return 'invalid' + return null } // normalize classicAddress:tag to X-address // TODO: Take network into account (!) -const normalize = (id: string, type: string) => { - if (type === 'transactions') { - return id.toUpperCase() +const normalizeAccount = (id: string) => { + if (!id.includes(':')) { + return id } - if (type === 'accounts' && id.includes(':')) { - // TODO: Test invalid classic address; "invalid" tag (?) - const components = id.split(':') - try { - const xAddress = classicAddressToXAddress( - components[0], - components[1] === undefined || components[1] === 'false' - ? false - : Number(components[1]), - false, - ) // TODO: Take network into account (!) - return xAddress - } catch (_) { - /* version_invalid: version bytes do not match any of the provided version(s) */ - } - } else if (type === 'paystrings') { - if (!isValidPayString(id)) { - return id.replace('@', '$') - } - } else if (type === 'token') { - const components = id.split(separators) - return `${components[0].toLowerCase()}.${components[1]}` + // TODO: Test invalid classic address; "invalid" tag (?) + const components = id.split(':') + try { + const xAddress = classicAddressToXAddress( + components[0], + components[1] === undefined || components[1] === 'false' + ? false + : Number(components[1]), + false, + ) // TODO: Take network into account (!) + return xAddress + } catch (_) { + /* version_invalid: version bytes do not match any of the provided version(s) */ } + return id } @@ -107,17 +149,13 @@ export const Search = ({ callback = () => {} }: SearchProps) => { const handleSearch = async (id: string) => { const strippedId = id.replace(/^["']|["']$/g, '') - const type = await getIdType(strippedId, socket) + const route = await getRoute(strippedId, socket) track('search', { search_term: strippedId, - search_category: type, + search_category: route?.type, }) - navigate( - type === 'invalid' - ? `/search/${strippedId}` - : `/${type}/${normalize(strippedId, type)}`, - ) + navigate(route === null ? `/search/${strippedId}` : route.path) callback() } diff --git a/src/containers/Header/index.tsx b/src/containers/Header/index.tsx index 92d7a8972..5ee021570 100644 --- a/src/containers/Header/index.tsx +++ b/src/containers/Header/index.tsx @@ -2,7 +2,7 @@ import { FC } from 'react' import classnames from 'classnames' import { Banner } from './Banner' -import { navigationConfig } from '../App/routes' +import { navigationConfig } from '../App/navigation' import { NavigationMenu } from './NavigationMenu' import './header.scss' diff --git a/src/containers/Header/test/Search.test.js b/src/containers/Header/test/Search.test.js index a085a6015..3d073fe20 100644 --- a/src/containers/Header/test/Search.test.js +++ b/src/containers/Header/test/Search.test.js @@ -210,4 +210,6 @@ describe('Search component', () => { expect(window.location.pathname).toEqual(`/transactions/${hash}`) wrapper.unmount() }) + + // TODO: Add custom search tests })