From cebc8f343f4e161c55cab53dd8d16700fb658657 Mon Sep 17 00:00:00 2001 From: Kristof Csillag Date: Wed, 21 Feb 2024 17:57:33 +0100 Subject: [PATCH] Revamp AccountLink and implement searching for account names --- src/app/components/Account/AccountLink.tsx | 186 ++++++++++++------ .../Account/ContractCreatorInfo.tsx | 25 ++- src/app/components/Account/index.tsx | 12 +- src/app/components/AccountList/index.tsx | 2 +- src/app/components/Search/search-utils.ts | 5 + .../StyledDescriptionList/index.tsx | 2 + src/app/components/Tokens/TokenHolders.tsx | 6 +- src/app/components/Tokens/TokenList.tsx | 6 +- src/app/components/Tokens/TokenTransfers.tsx | 4 +- .../Transactions/ConsensusTransactions.tsx | 4 +- .../Transactions/RuntimeTransactions.tsx | 10 +- src/app/data/pontusx-account-names.ts | 56 +++++- src/app/hooks/useAccountName.ts | 13 +- .../AccountDetailsPage/AccountDetailsCard.tsx | 3 + .../AccountDetailsPage/AccountDetailsView.tsx | 4 +- .../AccountNFTCollectionCard.tsx | 2 +- .../AccountDetailsPage/AccountTokensCard.tsx | 14 +- .../DeferredConsensusAccountDetails.tsx | 21 ++ .../DeferredRuntimeAccountDetails.tsx | 29 +++ src/app/pages/AccountDetailsPage/index.tsx | 3 +- .../InstanceTitleCard.tsx | 2 +- .../RuntimeTransactionDetailPage/index.tsx | 36 ++-- .../SearchResultsPage/SearchResultsList.tsx | 33 ++++ src/app/pages/SearchResultsPage/hooks.ts | 32 ++- .../useRedirectIfSingleResult.ts | 3 + src/app/pages/TokenDashboardPage/NFTLinks.tsx | 5 +- .../TokenDashboardPage/TokenDetailsCard.tsx | 6 +- .../TokenDashboardPage/TokenTitleCard.tsx | 6 +- src/styles/index.css | 4 + 29 files changed, 413 insertions(+), 121 deletions(-) create mode 100644 src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx create mode 100644 src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx diff --git a/src/app/components/Account/AccountLink.tsx b/src/app/components/Account/AccountLink.tsx index 31cd0953a1..4879dda96c 100644 --- a/src/app/components/Account/AccountLink.tsx +++ b/src/app/components/Account/AccountLink.tsx @@ -1,88 +1,156 @@ -import { FC } from 'react' +import { FC, ReactNode } from 'react' import { Link as RouterLink } from 'react-router-dom' import { useScreenSize } from '../../hooks/useScreensize' import Link from '@mui/material/Link' import { RouteUtils } from '../../utils/route-utils' +import InfoIcon from '@mui/icons-material/Info' import Typography from '@mui/material/Typography' import { COLORS } from '../../../styles/theme/colors' import { SearchScope } from '../../../types/searchScope' import { useAccountName } from '../../hooks/useAccountName' import { trimLongString } from '../../utils/trimLongString' -import Tooltip from '@mui/material/Tooltip' -import { tooltipDelay } from '../../../styles/theme' import Box from '@mui/material/Box' +import { HighlightedText } from '../HighlightedText' +import { AdaptiveHighlightedText } from '../HighlightedText/AdaptiveHighlightedText' +import { WithTooltip } from '../AdaptiveTrimmer/WithToolTip' +import { AdaptiveTrimmer } from '../AdaptiveTrimmer/AdaptiveTrimmer' + +const WithLink: FC<{ + content: ReactNode + to: string + plain?: boolean +}> = ({ content, to, plain }) => { + return ( + + {plain ? ( + content + ) : ( + + {content} + + )} + + ) +} export const AccountLink: FC<{ scope: SearchScope address: string + + /** + * Should we always trim the text to a short line? + */ alwaysTrim?: boolean + + /** + * Plain mode? (No link required) + */ plain?: boolean -}> = ({ scope, address, alwaysTrim, plain }) => { + + /** + * What part of the name should be highlighted (if any) + */ + highlightedPartOfName?: string | undefined + + /** + * Any extra tooltips to display + * + * (Besides the content necessary because of potential shortening) + */ + extraTooltip?: ReactNode +}> = ({ scope, address, alwaysTrim, plain, highlightedPartOfName, extraTooltip }) => { const { isTablet } = useScreenSize() const { name: accountName } = useAccountName(scope, address) const to = RouteUtils.getAccountRoute(scope, address) - const shortMode = alwaysTrim || isTablet // Do we want shortened lines? - - const nameDisplay = accountName ? ( - - {shortMode ? ( - - {trimLongString(accountName, 12, 0)} - - ) : ( - accountName - )} - + const tooltipPostfix = extraTooltip ? ( + <> + + {extraTooltip} + ) : undefined - const addressDisplay = shortMode ? ( - - {trimLongString(address, 6, 6)} - - ) : ( - address - ) + // Are we in a table? + if (alwaysTrim) { + // In a table, we only ever want one short line - const label = shortMode ? ( // Working with limited horizontal space - accountName ? ( // Do we want two lines ? - <> - {nameDisplay}({addressDisplay}) - - ) : ( - // No, only a single short line, either name or address - nameDisplay ?? addressDisplay + return ( + + {accountName} + {address} + {tooltipPostfix} + + ) : ( + address + ) + } + content={accountName ? trimLongString(accountName, 12, 0) : trimLongString(address, 6, 6)} + /> + } + to={to} + plain={plain} + /> ) - ) : // We can have a long line - accountName ? ( - `${accountName} (${address})` - ) : ( - address - ) + } + if (!isTablet) { + // Details in desktop mode. + // We want one long line, with name and address. + + return ( + + {address} + + ) : ( + address + ) + } + /> + } + to={to} + plain={plain} + /> + ) + } + + // We need to show the data in details mode on mobile. + // We want two lines, one for name (if available), one for address + // Both line adaptively shortened to fill available space return ( - + + + } - > - {plain ? ( - label - ) : ( - - {label} - - )} - + to={to} + plain={plain} + /> ) } diff --git a/src/app/components/Account/ContractCreatorInfo.tsx b/src/app/components/Account/ContractCreatorInfo.tsx index a944c83624..fed858109f 100644 --- a/src/app/components/Account/ContractCreatorInfo.tsx +++ b/src/app/components/Account/ContractCreatorInfo.tsx @@ -14,7 +14,11 @@ import Box from '@mui/material/Box' import Skeleton from '@mui/material/Skeleton' import { useScreenSize } from '../../hooks/useScreensize' -const TxSender: FC<{ scope: SearchScope; txHash: string }> = ({ scope, txHash }) => { +const TxSender: FC<{ scope: SearchScope; txHash: string; alwaysTrim?: boolean }> = ({ + scope, + txHash, + alwaysTrim, +}) => { const { t } = useTranslation() if (scope.layer === Layer.consensus) { throw AppErrors.UnsupportedLayer @@ -31,7 +35,7 @@ const TxSender: FC<{ scope: SearchScope; txHash: string }> = ({ scope, txHash }) }} /> ) : senderAddress ? ( - + ) : ( t('common.missing') ) @@ -41,7 +45,8 @@ export const ContractCreatorInfo: FC<{ scope: SearchScope isLoading?: boolean creationTxHash: string | undefined -}> = ({ scope, isLoading, creationTxHash }) => { + alwaysTrim?: boolean +}> = ({ scope, isLoading, creationTxHash, alwaysTrim }) => { const { t } = useTranslation() const { isMobile } = useScreenSize() @@ -59,9 +64,9 @@ export const ContractCreatorInfo: FC<{ minWidth: '25%', }} > - + {t('contract.createdAt')} - + ) } @@ -69,7 +74,8 @@ export const ContractCreatorInfo: FC<{ export const DelayedContractCreatorInfo: FC<{ scope: SearchScope contractOasisAddress: string | undefined -}> = ({ scope, contractOasisAddress }) => { + alwaysTrim?: boolean +}> = ({ scope, contractOasisAddress, alwaysTrim }) => { const accountQuery = useGetRuntimeAccountsAddress( scope.network, scope.layer as Runtime, @@ -82,6 +88,11 @@ export const DelayedContractCreatorInfo: FC<{ const creationTxHash = contract?.eth_creation_tx || contract?.creation_tx return ( - + ) } diff --git a/src/app/components/Account/index.tsx b/src/app/components/Account/index.tsx index 3658ba25de..ffa57144df 100644 --- a/src/app/components/Account/index.tsx +++ b/src/app/components/Account/index.tsx @@ -30,9 +30,17 @@ type AccountProps = { isLoading: boolean tokenPrices: AllTokenPrices showLayer?: boolean + highlightedPartOfName: string | undefined } -export const Account: FC = ({ account, token, isLoading, tokenPrices, showLayer }) => { +export const Account: FC = ({ + account, + token, + isLoading, + tokenPrices, + showLayer, + highlightedPartOfName, +}) => { const { t } = useTranslation() const { isMobile } = useScreenSize() const address = account ? account.address_eth ?? account.address : undefined @@ -67,7 +75,7 @@ export const Account: FC = ({ account, token, isLoading, tokenPric
- +
diff --git a/src/app/components/AccountList/index.tsx b/src/app/components/AccountList/index.tsx index 89050ff454..7f467c3568 100644 --- a/src/app/components/AccountList/index.tsx +++ b/src/app/components/AccountList/index.tsx @@ -39,7 +39,7 @@ export const AccountList: FC = ({ isLoading, limit, pagination key: 'size', }, { - content: , + content: , key: 'address', }, ...(verbose diff --git a/src/app/components/Search/search-utils.ts b/src/app/components/Search/search-utils.ts index 752c2fc784..c097b1eee9 100644 --- a/src/app/components/Search/search-utils.ts +++ b/src/app/components/Search/search-utils.ts @@ -112,6 +112,11 @@ export const validateAndNormalize = { return searchTerm.toLowerCase() } }, + accountNameFragment: (searchTerm: string) => { + if (searchTerm?.length >= textSearchMininumLength) { + return searchTerm.toLowerCase() + } + }, } satisfies { [name: string]: (searchTerm: string) => string | undefined } export function isSearchValid(searchTerm: string) { diff --git a/src/app/components/StyledDescriptionList/index.tsx b/src/app/components/StyledDescriptionList/index.tsx index 2ba5058ac5..08865f369a 100644 --- a/src/app/components/StyledDescriptionList/index.tsx +++ b/src/app/components/StyledDescriptionList/index.tsx @@ -61,6 +61,8 @@ export const StyledDescriptionList = styled(InlineDescriptionList, { color: COLORS.brandExtraDark, overflowWrap: 'anywhere', alignItems: 'center', + maxWidth: '100%', + overflowX: 'hidden', }, ...(standalone && { '&&': { diff --git a/src/app/components/Tokens/TokenHolders.tsx b/src/app/components/Tokens/TokenHolders.tsx index 77daeae628..8bc52e63b3 100644 --- a/src/app/components/Tokens/TokenHolders.tsx +++ b/src/app/components/Tokens/TokenHolders.tsx @@ -53,7 +53,11 @@ export const TokenHolders: FC = ({ { key: 'address', content: ( - + ), }, { diff --git a/src/app/components/Tokens/TokenList.tsx b/src/app/components/Tokens/TokenList.tsx index 9519e119d2..c1a979d7a5 100644 --- a/src/app/components/Tokens/TokenList.tsx +++ b/src/app/components/Tokens/TokenList.tsx @@ -107,7 +107,11 @@ export const TokenList = (props: TokensProps) => { { content: ( - + ), diff --git a/src/app/components/Tokens/TokenTransfers.tsx b/src/app/components/Tokens/TokenTransfers.tsx index fdf69be27e..a0b70bbfec 100644 --- a/src/app/components/Tokens/TokenTransfers.tsx +++ b/src/app/components/Tokens/TokenTransfers.tsx @@ -186,7 +186,7 @@ export const TokenTransfers: FC = ({ {trimLongString(fromAddress)} ) : ( - + )} @@ -210,7 +210,7 @@ export const TokenTransfers: FC = ({ {trimLongString(toAddress)} ) : ( - + ), }, ...(differentTokens diff --git a/src/app/components/Transactions/ConsensusTransactions.tsx b/src/app/components/Transactions/ConsensusTransactions.tsx index a700a754e2..fdc690a058 100644 --- a/src/app/components/Transactions/ConsensusTransactions.tsx +++ b/src/app/components/Transactions/ConsensusTransactions.tsx @@ -63,7 +63,7 @@ export const ConsensusTransactions: FC = ({ key: 'success', }, { - content: , + content: , key: 'hash', }, { @@ -97,7 +97,7 @@ export const ConsensusTransactions: FC = ({ diff --git a/src/app/components/Transactions/RuntimeTransactions.tsx b/src/app/components/Transactions/RuntimeTransactions.tsx index 8f432bc3a9..dd2519673b 100644 --- a/src/app/components/Transactions/RuntimeTransactions.tsx +++ b/src/app/components/Transactions/RuntimeTransactions.tsx @@ -102,11 +102,7 @@ export const RuntimeTransactions: FC = ({ : []), { content: ( - + ), key: 'hash', }, @@ -140,7 +136,7 @@ export const RuntimeTransactions: FC = ({ = ({ +type AccountEntry = { + name: string + address: string +} +type AccountData = { + map: AccountMap + list: AccountEntry[] +} const getPontusXAccountNames = () => - new Promise((resolve, reject) => { + new Promise((resolve, reject) => { axios.get(DATA_SOURCE_URL).then(response => { if (response.status !== 200) reject("Couldn't load names") if (!response.data) reject("Couldn't load names") - const result = new Map() + const map = new Map() + const list: AccountEntry[] = [] Object.entries(response.data).forEach(([address, name]) => { - result.set(address, name) + map.set(address, name) + const normalizedEntry: AccountEntry = { + name: name as string, + address, + } + list.push(normalizedEntry) + }) + resolve({ + map, + list, }) - resolve(result) }, reject) }) @@ -32,7 +52,33 @@ export const usePontusXAccountName = (address: string, enabled: boolean): Accoun console.log('Failed to load Pontus-X account names', error) } return { - name: allNames?.get(address), + name: allNames?.map.get(address), loading: isLoading, } } + +export const useSearchForPontusXAccountsByName = ( + network: Network, + nameFragment: string, + enabled: boolean, +) => { + const { isLoading, error, data: allNames } = usePontusXAccountNames(enabled) + if (error) { + console.log('Failed to load Pontus-X account names', error) + } + + const textMatcher = + nameFragment && enabled + ? (entry: AccountEntry): boolean => { + return !!findTextMatch(entry.name, [nameFragment]) + } + : () => false + return { + results: (allNames?.list || []).filter(textMatcher).map(entry => ({ + network, + layer: Layer.pontusx, + address: entry.address, + })), + isLoading, + } +} diff --git a/src/app/hooks/useAccountName.ts b/src/app/hooks/useAccountName.ts index 20e052bed9..fe7a95ea77 100644 --- a/src/app/hooks/useAccountName.ts +++ b/src/app/hooks/useAccountName.ts @@ -1,7 +1,7 @@ import { SearchScope } from '../../types/searchScope' import Chance from 'chance' import { Layer } from '../../oasis-nexus/api' -import { usePontusXAccountName } from '../data/pontusx-account-names' +import { usePontusXAccountName, useSearchForPontusXAccountsByName } from '../data/pontusx-account-names' const NO_MATCH = '__no_match__' @@ -59,3 +59,14 @@ export const useAccountName = (scope: SearchScope, address: string, dropCache = loading: false, } } + +export const useSearchForAccountsByName = (scope: SearchScope, nameFragment = '') => { + const isValidPontusXSearch = scope.layer === Layer.pontusx && !!nameFragment + const pontusXResults = useSearchForPontusXAccountsByName(scope.network, nameFragment, isValidPontusXSearch) + return isValidPontusXSearch + ? pontusXResults + : { + isLoading: false, + results: [], + } +} diff --git a/src/app/pages/AccountDetailsPage/AccountDetailsCard.tsx b/src/app/pages/AccountDetailsPage/AccountDetailsCard.tsx index 23bfe2975e..6f81242fe5 100644 --- a/src/app/pages/AccountDetailsPage/AccountDetailsCard.tsx +++ b/src/app/pages/AccountDetailsPage/AccountDetailsCard.tsx @@ -12,6 +12,7 @@ type AccountDetailsProps = { account: RuntimeAccount | undefined token: EvmToken | undefined tokenPrices: AllTokenPrices + highlightedPartOfName?: string | undefined } export const AccountDetailsCard: FC = ({ @@ -21,6 +22,7 @@ export const AccountDetailsCard: FC = ({ account, token, tokenPrices, + highlightedPartOfName, }) => { const { t } = useTranslation() return ( @@ -35,6 +37,7 @@ export const AccountDetailsCard: FC = ({ account={account} token={token} tokenPrices={tokenPrices} + highlightedPartOfName={highlightedPartOfName} /> ) diff --git a/src/app/pages/AccountDetailsPage/AccountDetailsView.tsx b/src/app/pages/AccountDetailsPage/AccountDetailsView.tsx index 0c9f702259..4e94e2a4d0 100644 --- a/src/app/pages/AccountDetailsPage/AccountDetailsView.tsx +++ b/src/app/pages/AccountDetailsPage/AccountDetailsView.tsx @@ -12,7 +12,8 @@ export const AccountDetailsView: FC<{ token?: EvmToken tokenPrices: AllTokenPrices showLayer?: boolean -}> = ({ isLoading, isError, account, token, tokenPrices, showLayer }) => { + highlightedPartOfName?: string | undefined +}> = ({ isLoading, isError, account, token, tokenPrices, showLayer, highlightedPartOfName }) => { const { t } = useTranslation() return isError ? ( @@ -23,6 +24,7 @@ export const AccountDetailsView: FC<{ isLoading={isLoading} tokenPrices={tokenPrices} showLayer={showLayer} + highlightedPartOfName={highlightedPartOfName} /> ) } diff --git a/src/app/pages/AccountDetailsPage/AccountNFTCollectionCard.tsx b/src/app/pages/AccountDetailsPage/AccountNFTCollectionCard.tsx index a03739e643..3ab574b8be 100644 --- a/src/app/pages/AccountDetailsPage/AccountNFTCollectionCard.tsx +++ b/src/app/pages/AccountDetailsPage/AccountNFTCollectionCard.tsx @@ -44,7 +44,7 @@ export const AccountNFTCollectionCard: FC = ({ scope, add isFetched && firstToken && ( - + ) diff --git a/src/app/pages/AccountDetailsPage/AccountTokensCard.tsx b/src/app/pages/AccountDetailsPage/AccountTokensCard.tsx index 71e15c55e7..b7b6f3a53f 100644 --- a/src/app/pages/AccountDetailsPage/AccountTokensCard.tsx +++ b/src/app/pages/AccountDetailsPage/AccountTokensCard.tsx @@ -30,10 +30,14 @@ type AccountTokensCardProps = AccountDetailsContext & { export const accountTokenContainerId = 'tokens' -export const ContractLink: FC<{ scope: SearchScope; address: string }> = ({ scope, address }) => { +export const ContractLink: FC<{ scope: SearchScope; address: string; alwaysTrim?: boolean }> = ({ + scope, + address, + alwaysTrim, +}) => { return ( - + ) @@ -79,7 +83,11 @@ export const AccountTokensCard: FC = ({ scope, account, { content: ( - + ), key: 'hash', diff --git a/src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx b/src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx new file mode 100644 index 0000000000..5720b4ecd7 --- /dev/null +++ b/src/app/pages/AccountDetailsPage/DeferredConsensusAccountDetails.tsx @@ -0,0 +1,21 @@ +import { FC } from 'react' +import { AllTokenPrices } from '../../../coin-gecko/api' +import { Network } from '../../../types/network' + +/** + * Load and display details of a RuntimeAccount + */ +export const DeferredConsensusAccountDetails: FC<{ + network: Network + address: string + tokenPrices: AllTokenPrices + highlightedPartOfName: string | undefined + showLayer?: boolean +}> = () => + // { + // network, address, tokenPrices, highlightedPartOfName, showLayer + // }, + { + // TODO: load and display consensus account details when API and component becomes available + return null + } diff --git a/src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx b/src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx new file mode 100644 index 0000000000..d0861aba6e --- /dev/null +++ b/src/app/pages/AccountDetailsPage/DeferredRuntimeAccountDetails.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react' +import { Layer, Runtime, useGetRuntimeAccountsAddress } from '../../../oasis-nexus/api' +import { AllTokenPrices } from '../../../coin-gecko/api' +import { AccountDetailsView } from './AccountDetailsView' +import { Network } from '../../../types/network' + +/** + * Load and display details of a RuntimeAccount + */ +export const DeferredRuntimeAccountDetails: FC<{ + network: Network + layer: Runtime + address: string + tokenPrices: AllTokenPrices + highlightedPartOfName: string | undefined + showLayer?: boolean +}> = ({ network, layer, address, tokenPrices, highlightedPartOfName, showLayer }) => { + const { data, isLoading, isError } = useGetRuntimeAccountsAddress(network, layer, address) + return ( + + ) +} diff --git a/src/app/pages/AccountDetailsPage/index.tsx b/src/app/pages/AccountDetailsPage/index.tsx index b35bdba1e1..01ecbcf5da 100644 --- a/src/app/pages/AccountDetailsPage/index.tsx +++ b/src/app/pages/AccountDetailsPage/index.tsx @@ -32,7 +32,7 @@ export const AccountDetailsPage: FC = () => { const { t } = useTranslation() const scope = useRequiredScopeParam() - const { address } = useLoaderData() as AddressLoaderData + const { address, searchTerm } = useLoaderData() as AddressLoaderData const { account, isLoading: isAccountLoading, isError } = useAccount(scope, address) const isContract = !!account?.evm_contract const { token, isLoading: isTokenLoading } = useTokenInfo(scope, address, isContract) @@ -62,6 +62,7 @@ export const AccountDetailsPage: FC = () => { account={account} token={token} tokenPrices={tokenPrices} + highlightedPartOfName={searchTerm} /> = ({ isFetched, isLoa }} > - + diff --git a/src/app/pages/RuntimeTransactionDetailPage/index.tsx b/src/app/pages/RuntimeTransactionDetailPage/index.tsx index 55cbeed5c8..a97835827a 100644 --- a/src/app/pages/RuntimeTransactionDetailPage/index.tsx +++ b/src/app/pages/RuntimeTransactionDetailPage/index.tsx @@ -270,24 +270,21 @@ export const RuntimeTransactionDetailView: FC<{ <>
{t('common.from')}
- - - + /> {from && }
@@ -297,21 +294,18 @@ export const RuntimeTransactionDetailView: FC<{ <>
{t('common.to')}
- - - + /> {to && }
diff --git a/src/app/pages/SearchResultsPage/SearchResultsList.tsx b/src/app/pages/SearchResultsPage/SearchResultsList.tsx index 366259b74c..c2903c46d6 100644 --- a/src/app/pages/SearchResultsPage/SearchResultsList.tsx +++ b/src/app/pages/SearchResultsPage/SearchResultsList.tsx @@ -7,6 +7,7 @@ import { RuntimeTransactionDetailView } from '../RuntimeTransactionDetailPage' import { AccountDetailsView } from '../AccountDetailsPage/AccountDetailsView' import { AccountResult, + AccountAddressResult, BlockResult, ContractResult, ProposalResult, @@ -21,6 +22,9 @@ import { AllTokenPrices } from '../../../coin-gecko/api' import { ResultListFrame } from './ResultListFrame' import { TokenDetails } from '../../components/Tokens/TokenDetails' import { ProposalDetailView } from '../ProposalDetailsPage' +import { DeferredRuntimeAccountDetails } from '../AccountDetailsPage/DeferredRuntimeAccountDetails' +import { Layer } from '../../../oasis-nexus/api' +import { DeferredConsensusAccountDetails } from '../AccountDetailsPage/DeferredConsensusAccountDetails' /** * Component for displaying a list of search results @@ -94,6 +98,35 @@ export const SearchResultsList: FC<{ linkLabel={t('search.results.accounts.viewLink')} /> + item.resultType === 'accountAddress', + )} + resultComponent={item => + item.layer === Layer.consensus ? ( + + ) : ( + + ) + } + link={acc => RouteUtils.getAccountRoute(acc, acc.address)} + linkLabel={t('search.results.accounts.viewLink')} + /> + item.resultType === 'contract')} diff --git a/src/app/pages/SearchResultsPage/hooks.ts b/src/app/pages/SearchResultsPage/hooks.ts index 395a96cbfb..b5450202e5 100644 --- a/src/app/pages/SearchResultsPage/hooks.ts +++ b/src/app/pages/SearchResultsPage/hooks.ts @@ -19,6 +19,7 @@ import { import { RouteUtils } from '../../utils/route-utils' import { SearchParams } from '../../components/Search/search-utils' import { SearchScope } from '../../../types/searchScope' +import { useSearchForAccountsByName } from '../../hooks/useAccountName' function isDefined(item: T): item is NonNullable { return item != null @@ -27,7 +28,7 @@ function isDefined(item: T): item is NonNullable { export type ConditionalResults = { isLoading: boolean; results: T[] } type SearchResultItemCore = HasScope & { - resultType: 'block' | 'transaction' | 'account' | 'contract' | 'token' | 'proposal' + resultType: 'block' | 'transaction' | 'account' | 'accountAddress' | 'contract' | 'token' | 'proposal' } export type BlockResult = SearchResultItemCore & RuntimeBlock & { resultType: 'block' } @@ -36,6 +37,10 @@ export type TransactionResult = SearchResultItemCore & RuntimeTransaction & { re export type AccountResult = SearchResultItemCore & RuntimeAccount & { resultType: 'account' } +export type AccountAddressResult = SearchResultItemCore & { address: string } & { + resultType: 'accountAddress' +} + export type ContractResult = SearchResultItemCore & RuntimeAccount & { resultType: 'contract' } export type TokenResult = SearchResultItemCore & EvmToken & { resultType: 'token' } @@ -46,6 +51,7 @@ export type SearchResultItem = | BlockResult | TransactionResult | AccountResult + | AccountAddressResult | ContractResult | TokenResult | ProposalResult @@ -193,6 +199,25 @@ export function useNetworkProposalsConditionally( } } +type AccountAddressInfo = Pick + +export function useNamedAccountConditionally( + currentScope: SearchScope | undefined, + nameFragment: string | undefined, +): ConditionalResults { + const queries = RouteUtils.getVisibleScopes(currentScope).map(scope => + // eslint-disable-next-line react-hooks/rules-of-hooks + useSearchForAccountsByName(scope, nameFragment), + ) + return { + isLoading: queries.some(query => query.isLoading), + results: queries + .map(query => query.results) + .filter(isDefined) + .flat(), + } +} + export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams) => { const queries = { blockHeight: useBlocksByHeightConditionally(currentScope, q.blockHeight), @@ -201,6 +226,7 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams oasisAccount: useRuntimeAccountConditionally(currentScope, q.consensusAccount), // TODO: remove evmBech32Account and use evmAccount when API is ready evmBech32Account: useRuntimeAccountConditionally(currentScope, q.evmBech32Account), + accountsByName: useNamedAccountConditionally(currentScope, q.accountNameFragment), tokens: useRuntimeTokenConditionally(currentScope, q.evmTokenNameFragment), proposals: useNetworkProposalsConditionally(q.networkProposalNameFragment), } @@ -211,6 +237,7 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams ...(queries.oasisAccount.results || []), ...(queries.evmBech32Account.results || []), ].filter(isAccountNonEmpty) + const accountAddresses = queries.accountsByName.results || [] const tokens = queries.tokens.results .map(l => l.evm_tokens) .flat() @@ -228,6 +255,9 @@ export const useSearch = (currentScope: SearchScope | undefined, q: SearchParams ...accounts .filter(account => account.evm_contract) .map((account): ContractResult => ({ ...account, resultType: 'contract' })), + ...accountAddresses.map( + (account): AccountAddressResult => ({ ...account, resultType: 'accountAddress' }), + ), ...tokens.map((token): TokenResult => ({ ...token, resultType: 'token' })), ...proposals.map((proposal): ProposalResult => ({ ...proposal, resultType: 'proposal' })), ] diff --git a/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts b/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts index ebcb816b8c..e3e704e029 100644 --- a/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts +++ b/src/app/pages/SearchResultsPage/useRedirectIfSingleResult.ts @@ -35,6 +35,9 @@ export function useRedirectIfSingleResult( case 'account': redirectTo = RouteUtils.getAccountRoute(item, item.address_eth ?? item.address) break + case 'accountAddress': + redirectTo = `${RouteUtils.getAccountRoute(item, item.address)}?q=${searchTerm}` + break case 'contract': redirectTo = RouteUtils.getAccountRoute(item, item.address_eth ?? item.address) break diff --git a/src/app/pages/TokenDashboardPage/NFTLinks.tsx b/src/app/pages/TokenDashboardPage/NFTLinks.tsx index e70dd170d3..a2c6979dce 100644 --- a/src/app/pages/TokenDashboardPage/NFTLinks.tsx +++ b/src/app/pages/TokenDashboardPage/NFTLinks.tsx @@ -66,8 +66,9 @@ export const NFTInstanceLink: FC = ({ scope, instance }) => { type NFTOwnerLinkProps = { scope: SearchScope owner: string + alwaysTrim?: boolean } -export const NFTOwnerLink: FC = ({ scope, owner }) => { +export const NFTOwnerLink: FC = ({ scope, owner, alwaysTrim }) => { const { t } = useTranslation() return ( @@ -76,7 +77,7 @@ export const NFTOwnerLink: FC = ({ scope, owner }) => { i18nKey="nft.ownerLink" t={t} components={{ - OwnerLink: , + OwnerLink: , }} /> diff --git a/src/app/pages/TokenDashboardPage/TokenDetailsCard.tsx b/src/app/pages/TokenDashboardPage/TokenDetailsCard.tsx index add80eb64e..8066b83498 100644 --- a/src/app/pages/TokenDashboardPage/TokenDetailsCard.tsx +++ b/src/app/pages/TokenDashboardPage/TokenDetailsCard.tsx @@ -69,7 +69,11 @@ export const TokenDetailsCard: FC<{ scope: SearchScope; address: string; searchT
{t('contract.creator')}
- +
{t('common.balance')}
diff --git a/src/app/pages/TokenDashboardPage/TokenTitleCard.tsx b/src/app/pages/TokenDashboardPage/TokenTitleCard.tsx index cfad3b2185..fdfb5ab8ae 100644 --- a/src/app/pages/TokenDashboardPage/TokenTitleCard.tsx +++ b/src/app/pages/TokenDashboardPage/TokenTitleCard.tsx @@ -25,7 +25,11 @@ export const TokenTitleCard: FC<{ scope: SearchScope; address: string; searchTer {token && ( <> - + )} diff --git a/src/styles/index.css b/src/styles/index.css index ad28f09c7c..951af7f1b0 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -19,3 +19,7 @@ html { svg.recharts-surface { overflow: visible; } + +.MuiTableBody-root span { + display: inline-flex; +}