Skip to content

Commit

Permalink
Support searching for named accounts by name
Browse files Browse the repository at this point in the history
  • Loading branch information
csillag committed May 21, 2024
1 parent 63ba9f2 commit 4186bd7
Show file tree
Hide file tree
Showing 20 changed files with 379 additions and 53 deletions.
11 changes: 7 additions & 4 deletions src/app/components/Account/AccountLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ interface Props {
/**
* Should we always trim the text to a short line when on mobile or Tablet?
*/
alwaysTrimOnTable?: boolean
alwaysTrimOnTablet?: boolean

/**
* What part of the name should be highlighted (if any)
Expand All @@ -82,13 +82,16 @@ export const AccountLink: FC<Props> = ({
scope,
address,
alwaysTrim,
alwaysTrimOnTable,
alwaysTrimOnTablet,
highlightedPartOfName,
extraTooltip,
labelOnly,
}) => {
const { isTablet } = useScreenSize()
const { metadata: accountMetadata } = useAccountMetadata(scope, address)
const {
metadata: accountMetadata,
// isError, // Use this to indicate that we have failed to load the name for this account
} = useAccountMetadata(scope, address)
const accountName = accountMetadata?.name // TODO: we should also use the description

const to = RouteUtils.getAccountRoute(scope, address)
Expand All @@ -108,7 +111,7 @@ export const AccountLink: FC<Props> = ({
) : undefined

// Are we in a situation when we should always trim?
if (alwaysTrim || (alwaysTrimOnTable && isTablet)) {
if (alwaysTrim || (alwaysTrimOnTablet && isTablet)) {
// In a table, we only ever want a short line

return (
Expand Down
12 changes: 10 additions & 2 deletions src/app/components/Account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type RuntimeAccountDataProps = {
isLoading: boolean
tokenPrices: AllTokenPrices
showLayer?: boolean
highlightedPartOfName: string | undefined
}

export const RuntimeAccountData: FC<RuntimeAccountDataProps> = ({
Expand All @@ -41,6 +42,7 @@ export const RuntimeAccountData: FC<RuntimeAccountDataProps> = ({
isLoading,
tokenPrices,
showLayer,
highlightedPartOfName,
}) => {
const { t } = useTranslation()
const { isMobile } = useScreenSize()
Expand Down Expand Up @@ -76,7 +78,7 @@ export const RuntimeAccountData: FC<RuntimeAccountDataProps> = ({
<AccountAvatar account={account} />
</StyledListTitleWithAvatar>
<dd>
<AccountLink scope={account} address={address!} />
<AccountLink scope={account} address={address!} highlightedPartOfName={highlightedPartOfName} />
<CopyToClipboard value={address!} />
</dd>

Expand Down Expand Up @@ -175,13 +177,15 @@ export type ConsensusAccountDataProps = {
isLoading?: boolean
showLayer?: boolean
standalone?: boolean
highlightedPartOfName?: string | undefined
}

export const ConsensusAccountData: FC<ConsensusAccountDataProps> = ({
account,
isLoading,
showLayer,
standalone,
highlightedPartOfName,
}) => {
const { t } = useTranslation()
const { isMobile } = useScreenSize()
Expand All @@ -205,7 +209,11 @@ export const ConsensusAccountData: FC<ConsensusAccountDataProps> = ({
</Box>
</StyledListTitleWithAvatar>
<dd>
<AccountLink scope={account} address={account.address} />
<AccountLink
scope={account}
address={account.address}
highlightedPartOfName={highlightedPartOfName}
/>
<CopyToClipboard value={account.address} />
</dd>
<dt>
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/RuntimeEvents/RuntimeEventDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const EvmEventParamData: FC<{
// TODO: handle more EVM types
case 'address':
return address ? (
<AccountLink address={address} scope={scope} alwaysTrimOnTable={alwaysTrimOnTable} />
<AccountLink address={address} scope={scope} alwaysTrimOnTablet={alwaysTrimOnTable} />
) : null
case 'uint256':
// TODO: format with BigNumber
Expand Down
5 changes: 5 additions & 0 deletions src/app/components/Search/search-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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) {
Expand Down
36 changes: 35 additions & 1 deletion src/app/data/named-accounts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Network } from '../../types/network'
import { Account, Layer, Runtime, RuntimeAccount } from '../../oasis-nexus/api'

export type AccountMetadata = {
address: string
name?: string
Expand All @@ -6,7 +9,8 @@ export type AccountMetadata = {

export type AccountMetadataInfo = {
metadata?: AccountMetadata
loading: boolean
isLoading: boolean
isError: boolean
}

export type AccountMap = Map<string, AccountMetadata>
Expand All @@ -15,3 +19,33 @@ export type AccountData = {
map: AccountMap
list: AccountMetadata[]
}

export type AccountNameSearchMatch = {
network: Network
layer: Layer
address: string
}

export type AccountNameSearchRuntimeMatch = {
network: Network
layer: Runtime
address: string
}

export type AccountNameSearchConsensusMatch = {
network: Network
layer: typeof Layer.consensus
address: string
}

export type AccountNameSearchResults = {
results: (Account | RuntimeAccount)[] | undefined
isLoading: boolean
isError: boolean
}

export type AccountNameSearchRuntimeResults = {
results: RuntimeAccount[] | undefined
isLoading: boolean
isError: boolean
}
91 changes: 83 additions & 8 deletions src/app/data/oasis-account-names.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import axios from 'axios'
import { useQuery } from '@tanstack/react-query'
import { Layer } from '../../oasis-nexus/api'
import {
Layer,
useGetConsensusAccountsAddresses,
useGetRuntimeAccountsAddresses,
} from '../../oasis-nexus/api'
import { Network } from '../../types/network'
import { AccountData, AccountMap, AccountMetadata, AccountMetadataInfo } from './named-accounts'
import {
AccountData,
AccountMap,
AccountMetadata,
AccountMetadataInfo,
AccountNameSearchConsensusMatch,
AccountNameSearchMatch,
AccountNameSearchResults,
AccountNameSearchRuntimeMatch,
} from './named-accounts'
import { hasTextMatch } from '../components/HighlightedText/text-matching'

const dataSources: Record<Network, Partial<Record<Layer, string>>> = {
[Network.mainnet]: {
Expand Down Expand Up @@ -48,9 +62,13 @@ const getOasisAccountsMetadata = async (network: Network, layer: Layer): Promise
}
}

export const useOasisAccountsMetadata = (network: Network, layer: Layer, enabled: boolean) => {
export const useOasisAccountsMetadata = (
network: Network,
layer: Layer,
queryOptions: { enabled: boolean },
) => {
return useQuery(['oasisAccounts', network, layer], () => getOasisAccountsMetadata(network, layer), {
enabled,
enabled: queryOptions.enabled,
staleTime: Infinity,
})
}
Expand All @@ -59,14 +77,71 @@ export const useOasisAccountMetadata = (
network: Network,
layer: Layer,
address: string,
enabled: boolean,
queryOptions: { enabled: boolean },
): AccountMetadataInfo => {
const { isLoading, error, data: allData } = useOasisAccountsMetadata(network, layer, enabled)
if (error) {
const { isLoading, isError, error, data: allData } = useOasisAccountsMetadata(network, layer, queryOptions)
if (isError) {
console.log('Failed to load Oasis account metadata', error)
}
return {
metadata: allData?.map.get(address),
loading: isLoading,
isLoading,
isError,
}
}

export const useSearchForOasisAccountsByName = (
network: Network,
layer: Layer,
nameFragment: string,
queryOptions: { enabled: boolean },
): AccountNameSearchResults => {
const {
isLoading: isMetadataLoading,
isError: isMetadataError,
error: metadataError,
data: namedAccounts,
} = useOasisAccountsMetadata(network, layer, queryOptions)
if (isMetadataError) {
console.log('Failed to load Oasis account metadata', metadataError)
}

const textMatcher =
nameFragment && queryOptions.enabled
? (account: AccountMetadata) => hasTextMatch(account.name, [nameFragment])
: () => false

const matches =
namedAccounts?.list.filter(textMatcher).map(
(account): AccountNameSearchMatch => ({
network,
layer,
address: account.address,
}),
) ?? []

const consensusMatches = layer === Layer.consensus ? (matches as AccountNameSearchConsensusMatch[]) : []
const runtimeMatches = layer === Layer.consensus ? [] : (matches as AccountNameSearchRuntimeMatch[])

const {
isLoading: areConsensusAccountsLoading,
isError: areConsensusAccountsError,
data: consensusResults,
} = useGetConsensusAccountsAddresses(consensusMatches, {
enabled: queryOptions.enabled && !isMetadataLoading && !isMetadataError,
})

const {
isLoading: areRuntimeAccountsLoading,
isError: areRuntimeAccountsError,
data: runtimeResults,
} = useGetRuntimeAccountsAddresses(runtimeMatches, {
enabled: queryOptions.enabled && !isMetadataLoading && !isMetadataError,
})

return {
isLoading: isMetadataLoading || areConsensusAccountsLoading || areRuntimeAccountsLoading,
isError: isMetadataError || areConsensusAccountsError || areRuntimeAccountsError,
results: [...consensusResults, ...runtimeResults],
}
}
89 changes: 75 additions & 14 deletions src/app/data/pontusx-account-names.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import axios from 'axios'
import { useQuery } from '@tanstack/react-query'
import { AccountMetadata, AccountMap, AccountMetadataInfo } from './named-accounts'
import {
AccountMetadata,
AccountMap,
AccountMetadataInfo,
AccountNameSearchRuntimeMatch,
AccountNameSearchRuntimeResults,
} from './named-accounts'
import { Layer, useGetRuntimeAccountsAddresses } from '../../oasis-nexus/api'
import { Network } from '../../types/network'
import { hasTextMatch } from '../components/HighlightedText/text-matching'
import { getOasisAddress } from '../utils/helpers'

const DATA_SOURCE_URL = 'https://raw.githubusercontent.com/deltaDAO/mvg-portal/main/pontusxAddresses.json'

Expand All @@ -10,35 +20,86 @@ const getPontusXAccountsMetadata = async () => {
if (!response.data) throw new Error("Couldn't load names")
const map: AccountMap = new Map()
const list: AccountMetadata[] = []
Object.entries(response.data).forEach(([address, name]) => {
map.set(address, { address, name: name as string })
list.push({
Object.entries(response.data).forEach(([evmAddress, name]) => {
const account: AccountMetadata = {
address: getOasisAddress(evmAddress),
name: name as string,
address,
})
}
map.set(evmAddress, account)
list.push(account)
})
return {
map,
list,
}
}

export const usePontusXAccountsMetadata = (enabled: boolean) => {
export const usePontusXAccountsMetadata = (queryOptions: { enabled: boolean }) => {
return useQuery(['pontusXNames'], getPontusXAccountsMetadata, {
enabled,
enabled: queryOptions.enabled,
staleTime: Infinity,
})
}

export const usePontusXAccountMetadata = (address: string, enabled: boolean): AccountMetadataInfo => {
// This is not a condition that can change while the app is running, so it's OK.
// eslint-disable-next-line react-hooks/rules-of-hooks
const { isLoading, error, data: allData } = usePontusXAccountsMetadata(enabled)
if (error) {
export const usePontusXAccountMetadata = (
address: string,
queryOptions: { enabled: boolean },
): AccountMetadataInfo => {
const { isLoading, isError, error, data: allData } = usePontusXAccountsMetadata(queryOptions)
if (isError) {
console.log('Failed to load Pontus-X account names', error)
}
return {
metadata: allData?.map.get(address),
loading: isLoading,
isLoading,
isError,
}
}

export const useSearchForPontusXAccountsByName = (
network: Network,
nameFragment: string,
queryOptions: { enabled: boolean },
): AccountNameSearchRuntimeResults => {
const {
isLoading: isMetadataLoading,
isError: isMetadataError,
error: metadataError,
data: namedAccounts,
} = usePontusXAccountsMetadata(queryOptions)
if (isMetadataError) {
console.log('Failed to load Pontus-X account names', metadataError)
}

const textMatcher =
nameFragment && queryOptions.enabled
? (account: AccountMetadata) => hasTextMatch(account.name, [nameFragment])
: () => false

const matches =
isMetadataLoading || isMetadataLoading
? undefined
: namedAccounts?.list.filter(textMatcher).map(
(account): AccountNameSearchRuntimeMatch => ({
network,
layer: Layer.pontusx,
address: account.address,
}),
)

if (matches?.length) console.log('PontusX matches to load:', matches)

const {
isLoading: areAccountsLoading,
isError: areAccountsError,
data: results,
} = useGetRuntimeAccountsAddresses(matches, {
enabled: queryOptions.enabled && !isMetadataLoading && !isMetadataError,
})

return {
isLoading: isMetadataLoading || areAccountsLoading,
isError: isMetadataError || areAccountsError,
results,
}
}
Loading

0 comments on commit 4186bd7

Please sign in to comment.