Skip to content

Commit

Permalink
Merge pull request #637 from oasisprotocol/csillag/search-for-token
Browse files Browse the repository at this point in the history
Support searching for tokens by name or symbol
  • Loading branch information
csillag authored Jul 3, 2023
2 parents 48e5791 + 1f5b744 commit 6cb79b7
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 9 deletions.
1 change: 1 addition & 0 deletions .changelog/637.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support searching for tokens by name
5 changes: 4 additions & 1 deletion src/app/components/Search/SearchSuggestionsButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { COLORS } from '../../../styles/theme/colors'
import WidgetsIcon from '@mui/icons-material/Widgets'
import RepeatIcon from '@mui/icons-material/Repeat'
import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'
import TokenIcon from '@mui/icons-material/Token'
import { searchSuggestionTerms } from './search-utils'
import { OptionalBreak } from '../OptionalBreak'
import { SearchScope } from '../../../types/searchScope'
Expand Down Expand Up @@ -35,7 +36,7 @@ interface Props {

export const SearchSuggestionsButtons: FC<Props> = ({ scope, onClickSuggestion }) => {
const { t } = useTranslation()
const { suggestedBlock, suggestedTransaction, suggestedAccount } =
const { suggestedBlock, suggestedTransaction, suggestedAccount, suggestedTokenFragment } =
(scope?.network && scope?.layer && searchSuggestionTerms[scope.network][scope.layer]) ??
searchSuggestionTerms['mainnet']['sapphire']!

Expand All @@ -53,6 +54,8 @@ export const SearchSuggestionsButtons: FC<Props> = ({ scope, onClickSuggestion }
TransactionLink: <SuggestionButton onClick={() => onClickSuggestion(suggestedTransaction)} />,
AccountIcon: <AccountBalanceWalletIcon sx={{ fontSize: '18px' }} />,
AccountLink: <SuggestionButton onClick={() => onClickSuggestion(suggestedAccount)} />,
TokenIcon: <TokenIcon sx={{ fontSize: '18px' }} />,
TokenLink: <SuggestionButton onClick={() => onClickSuggestion(suggestedTokenFragment)} />,
}}
/>
</Typography>
Expand Down
6 changes: 5 additions & 1 deletion src/app/components/Search/SearchSuggestionsLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface Props {

export const SearchSuggestionsLinks: FC<Props> = ({ scope }) => {
const { t } = useTranslation()
const { suggestedBlock, suggestedTransaction, suggestedAccount } =
const { suggestedBlock, suggestedTransaction, suggestedAccount, suggestedTokenFragment } =
(scope?.network && scope?.layer && searchSuggestionTerms[scope.network][scope.layer]) ??
searchSuggestionTerms['mainnet']['sapphire']!

Expand All @@ -31,6 +31,10 @@ export const SearchSuggestionsLinks: FC<Props> = ({ scope }) => {
),
AccountIcon: <></>,
AccountLink: <Link component={RouterLink} to={RouteUtils.getSearchRoute(scope, suggestedAccount)} />,
TokenIcon: <></>,
TokenLink: (
<Link component={RouterLink} to={RouteUtils.getSearchRoute(scope, suggestedTokenFragment)} />
),
}}
/>
)
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Search/__tests__/search-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('search-utils validateAndNormalize', () => {
it('isSearchValid', () => {
expect(isSearchValid(consensusTxHash)).toBe(true)
expect(isSearchValid(blockHeight)).toBe(true)
expect(isSearchValid(blockHeight.replace('1', 'a'))).toBe(false)
expect(isSearchValid('yu')).toBe(false)
expect(isSearchValid('yuz')).toBe(true)
})
})
9 changes: 9 additions & 0 deletions src/app/components/Search/search-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type LayerSuggestions = {
suggestedBlock: string
suggestedTransaction: string
suggestedAccount: string
suggestedTokenFragment: string
}

export const searchSuggestionTerms: Record<Network, Partial<Record<Layer, LayerSuggestions>>> = {
Expand All @@ -24,18 +25,21 @@ export const searchSuggestionTerms: Record<Network, Partial<Record<Layer, LayerS
suggestedBlock: '4260',
suggestedTransaction: '0x2f461f83745e1fa1177138aa815e210e1c69305db8065af9015b2e490a5033f1',
suggestedAccount: '0x0266562AB0aE2a80C14373029a70F73A9A3dB9d3',
suggestedTokenFragment: 'yuzu',
},
sapphire: {
suggestedBlock: '4260',
suggestedTransaction: '0x5900415a3fbb39325d5dfe145d1eccd1586a2afe12a204de34ecac0c808ac3f7',
suggestedAccount: '0x90adE3B7065fa715c7a150313877dF1d33e777D5',
suggestedTokenFragment: 'mock',
},
},
testnet: {
sapphire: {
suggestedBlock: '4260',
suggestedTransaction: '0xd9b5c08be1cb74229abedd9b3e1afb8b43228085a6abf72993db415959ab6b35',
suggestedAccount: '0xfA3AC9f65C9D75EE3978ab76c6a1105f03156204',
suggestedTokenFragment: 'USD',
},
},
} satisfies SpecifiedPerEnabledLayer
Expand Down Expand Up @@ -80,6 +84,11 @@ export const validateAndNormalize = {
return searchTerm.toLowerCase()
}
},
evmTokenNameFragment: (searchTerm: string) => {
if (searchTerm?.length > 2) {
return searchTerm.toLowerCase()
}
},
} satisfies { [name: string]: (searchTerm: string) => string | undefined }

export function isSearchValid(searchTerm: string) {
Expand Down
11 changes: 10 additions & 1 deletion src/app/pages/SearchResultsPage/SearchResultsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { BlockDetailView } from '../BlockDetailPage'
import { RouteUtils } from '../../utils/route-utils'
import { TransactionDetailView } from '../TransactionDetailPage'
import { AccountDetailsView } from '../AccountDetailsPage'
import { AccountResult, BlockResult, SearchResults, TransactionResult } from './hooks'
import { AccountResult, BlockResult, SearchResults, TokenResult, TransactionResult } from './hooks'
import { getThemesForNetworks } from '../../../styles/theme'
import { Network } from '../../../types/network'
import { SubPageCard } from '../../components/SubPageCard'
import { AllTokenPrices } from '../../../coin-gecko/api'
import { ResultListFrame } from './ResultListFrame'
import { TokenDetails } from '../../components/Tokens/TokenDetails'

/**
* Component for displaying a list of search results
Expand Down Expand Up @@ -82,6 +83,14 @@ export const SearchResultsList: FC<{
link={acc => RouteUtils.getAccountRoute(acc, acc.address_eth ?? acc.address)}
linkLabel={t('search.results.accounts.viewLink')}
/>

<ResultsGroupByType
title={t('search.results.tokens.title')}
results={searchResults.filter((item): item is TokenResult => item.resultType === 'token')}
resultComponent={item => <TokenDetails token={item} showLayer />}
link={token => RouteUtils.getTokenRoute(token, token.eth_contract_addr ?? token.contract_addr)}
linkLabel={t('search.results.tokens.viewLink')}
/>
</SubPageCard>
</ResultListFrame>
)
Expand Down
45 changes: 43 additions & 2 deletions src/app/pages/SearchResultsPage/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
Layer,
isAccountNonEmpty,
HasScope,
useGetRuntimeEvmTokens,
EvmTokenList,
EvmToken,
} from '../../../oasis-nexus/api'
import { RouteUtils } from '../../utils/route-utils'
import { SearchParams } from '../../components/Search/search-utils'
Expand All @@ -20,7 +23,7 @@ function isDefined<T>(item: T): item is NonNullable<T> {
export type ConditionalResults<T> = { isLoading: boolean; results: T[] }

type SearchResultItemCore = HasScope & {
resultType: 'block' | 'transaction' | 'account'
resultType: 'block' | 'transaction' | 'account' | 'token'
}

export type BlockResult = SearchResultItemCore & RuntimeBlock & { resultType: 'block' }
Expand All @@ -29,7 +32,9 @@ export type TransactionResult = SearchResultItemCore & RuntimeTransaction & { re

export type AccountResult = SearchResultItemCore & RuntimeAccount & { resultType: 'account' }

export type SearchResultItem = BlockResult | TransactionResult | AccountResult
export type TokenResult = SearchResultItemCore & EvmToken & { resultType: 'token' }

export type SearchResultItem = BlockResult | TransactionResult | AccountResult | TokenResult

export type SearchResults = SearchResultItem[]

Expand Down Expand Up @@ -96,6 +101,35 @@ export function useRuntimeAccountConditionally(
}
}

export function useRuntimeTokenConditionally(
nameFragment: string | undefined,
): ConditionalResults<EvmTokenList> {
const queries = RouteUtils.getEnabledScopes()
.filter(scope => scope.layer !== Layer.consensus)
.map(scope =>
// See explanation above
// eslint-disable-next-line react-hooks/rules-of-hooks
useGetRuntimeEvmTokens(
scope.network,
scope.layer as Runtime,
{
name: nameFragment,
limit: 10,
},
{
query: {
enabled: !!nameFragment,
},
},
),
)

return {
isLoading: queries.some(query => query.isInitialLoading),
results: queries.map(query => query.data?.data).filter(isDefined),
}
}

export const useSearch = (q: SearchParams) => {
const queries = {
blockHeight: useBlocksConditionally(q.blockHeight),
Expand All @@ -104,6 +138,7 @@ export const useSearch = (q: SearchParams) => {
oasisAccount: useRuntimeAccountConditionally(q.consensusAccount),
// TODO: remove evmBech32Account and use evmAccount when API is ready
evmBech32Account: useRuntimeAccountConditionally(q.evmBech32Account),
tokens: useRuntimeTokenConditionally(q.evmTokenNameFragment),
}
const isLoading = Object.values(queries).some(query => query.isLoading)
const blocks = queries.blockHeight.results || []
Expand All @@ -112,12 +147,18 @@ export const useSearch = (q: SearchParams) => {
...(queries.oasisAccount.results || []),
...(queries.evmBech32Account.results || []),
].filter(isAccountNonEmpty)
const tokens = queries.tokens.results
.map(l => l.evm_tokens)
.flat()
.sort((t1, t2) => t2.num_holders - t1.num_holders)

const results: SearchResultItem[] = isLoading
? []
: [
...blocks.map((block): BlockResult => ({ ...block, resultType: 'block' })),
...transactions.map((tx): TransactionResult => ({ ...tx, resultType: 'transaction' })),
...accounts.map((account): AccountResult => ({ ...account, resultType: 'account' })),
...tokens.map((token): TokenResult => ({ ...token, resultType: 'token' })),
]
return {
isLoading,
Expand Down
3 changes: 3 additions & 0 deletions src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export function useRedirectIfSingleResult(scope: SearchScope | undefined, result
case 'account':
redirectTo = RouteUtils.getAccountRoute(item, item.address_eth ?? item.address)
break
case 'token':
redirectTo = RouteUtils.getTokenRoute(item, item.eth_contract_addr || item.contract_addr)
break
default:
exhaustedTypeWarning('Unexpected result type', item)
}
Expand Down
10 changes: 7 additions & 3 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,8 @@
"selectNetworkAria": "Toggle network selector"
},
"search": {
"placeholder": "Address, Block, Contract, Txn Hash, Transaction ID, etc",
"mobilePlaceholder": "Search Address, Block, Txn, etc",
"placeholder": "Address, Block, Contract, Txn Hash, Transaction ID, Token name, etc",
"mobilePlaceholder": "Search Address, Block, Txn, Token, etc",
"noResults": {
"header": "No results found",
"scopeHeader": "No results found on {{scope}}",
Expand All @@ -379,6 +379,10 @@
"title": "Blocks",
"viewLink": "View Block"
},
"tokens": {
"title": "Tokens",
"viewLink": "View Token"
},
"transactions": {
"title": "Transactions",
"viewLink": "View Transaction"
Expand All @@ -389,7 +393,7 @@
"moreCount_other": "{{ count }} more results"
},
"searchBtnText": "Search",
"searchSuggestions": "Not sure what to look for? Try out a search: <OptionalBreak><BlockLink><BlockIcon/> Block</BlockLink>, <TransactionLink><TransactionIcon/> Transaction</TransactionLink>, <AccountLink><AccountIcon/> Address</AccountLink></OptionalBreak>",
"searchSuggestions": "Not sure what to look for? Try out a search: <OptionalBreak><BlockLink><BlockIcon/> Block</BlockLink>, <TransactionLink><TransactionIcon/> Transaction</TransactionLink>, <AccountLink><AccountIcon/> Address</AccountLink>, <TokenLink><TokenIcon/> Token</TokenLink> </OptionalBreak>",
"sectionHeader": "Results on {{ scope }}"
}
}

0 comments on commit 6cb79b7

Please sign in to comment.