Files
explore/ui/token/TokenPageTitle.tsx
tom goriunovandGitHub ebbee2ba57 Multichain explorer v2 (#3259)
* Support redirect to a correct view from search

* show domains on address page

* migrate to new API version to display address domains

* another api update

* new design for chain widget on home page + tests

* chain widget styles for mobile devices

* ecosystems page

* update types

* add ecosystems to navigation

* address portfolio: pt.1

* portfolio tokens list

* filter portfolio by chain

* skip redirect when navigating to search result by clicking on "show all results" link

* net worth block design

* add filter input to chain select and fix tokens table

* calculate address portfolio

* adjust card logic

* support custom token types in multichain explorer

* handle case when new worth is 0

* add text filter to tokens list

* fixes

* demo envs

* portfolio tests

* ecosystems test

* change signature for getTokenTypes function

* fix adding token to the wallet from the list

* lazy load all address domains

* show contract creator and implementations info

* fix tests

* add poor reputation tokens toggle

* tweaks

* dim celo native tokens

* disable env validator

* fix tests

* remove tokens with 0 usd value from top tokens

* fixes

* preserve selected chain when switching tabs on address page

* add hints to stats widgets on home page

* update envs for demo

* fix portfolio cards and home page

* update styles for contract creator and implementations

* fix select issues

* add chains from cluster to network menu

* hide csv export link on empty logs page

* update screenshots

* rollback select fix

* update cut length calculation for portfolio cards

* fix chain select

* update screenshots

* fix tests

* more fixes

* add sorting to ecosystems table

* fix test

* fixes

* fix loading state in ecosystems page

* review fixes

* fix tests
2026-02-25 10:45:09 +01:00

155 lines
6.3 KiB
TypeScript

import { Flex, useToken } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
import type { Address } from 'types/api/address';
import type { TokenInfo, TokenVerifiedInfo as TTokenVerifiedInfo } from 'types/api/token';
import type { EntityTag } from 'ui/shared/EntityTags/types';
import config from 'configs/app';
import useAddressMetadataInfoQuery from 'lib/address/useAddressMetadataInfoQuery';
import type { ResourceError } from 'lib/api/resources';
import { useMultichainContext } from 'lib/contexts/multichain';
import { getTokenTypeName } from 'lib/token/tokenTypes';
import { Tooltip } from 'toolkit/chakra/tooltip';
import AddressAlerts from 'ui/address/details/AddressAlerts';
import AddressQrCode from 'ui/address/details/AddressQrCode';
import AccountActionsMenu from 'ui/shared/AccountActionsMenu/AccountActionsMenu';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as TokenEntity from 'ui/shared/entities/token/TokenEntity';
import EntityTags from 'ui/shared/EntityTags/EntityTags';
import formatUserTags from 'ui/shared/EntityTags/formatUserTags';
import sortEntityTags from 'ui/shared/EntityTags/sortEntityTags';
import IconSvg from 'ui/shared/IconSvg';
import NetworkExplorers from 'ui/shared/NetworkExplorers';
import PageTitle from 'ui/shared/Page/PageTitle';
import TokenVerifiedInfo from './TokenVerifiedInfo';
const PREDEFINED_TAG_PRIORITY = 100;
interface Props {
tokenQuery: UseQueryResult<TokenInfo, ResourceError<unknown>>;
addressQuery: UseQueryResult<Address, ResourceError<unknown>>;
verifiedInfoQuery: UseQueryResult<TTokenVerifiedInfo, ResourceError<unknown>>;
hash: string;
}
const TokenPageTitle = ({ tokenQuery, addressQuery, verifiedInfoQuery, hash }: Props) => {
const multichainContext = useMultichainContext();
const addressHash = !tokenQuery.isPlaceholderData ? (tokenQuery.data?.address_hash || '') : '';
const addressesForMetadataQuery = React.useMemo(() => ([ hash ].filter(Boolean)), [ hash ]);
const addressMetadataQuery = useAddressMetadataInfoQuery(addressesForMetadataQuery);
const isLoading = tokenQuery.isPlaceholderData ||
addressQuery.isPlaceholderData ||
(config.features.verifiedTokens.isEnabled && verifiedInfoQuery.isPending);
const tokenSymbolText = tokenQuery.data?.symbol ? ` (${ tokenQuery.data.symbol })` : '';
const [ bridgedTokenTagBgColor ] = useToken('colors', 'blue.500');
const [ bridgedTokenTagTextColor ] = useToken('colors', 'white');
const tags: Array<EntityTag> = React.useMemo(() => {
return [
tokenQuery.data ? {
slug: tokenQuery.data?.type,
name: getTokenTypeName(tokenQuery.data.type, multichainContext?.chain?.app_config),
tagType: 'custom' as const,
ordinal: PREDEFINED_TAG_PRIORITY,
} : undefined,
config.features.bridgedTokens.isEnabled && tokenQuery.data?.is_bridged ?
{
slug: 'bridged',
name: 'Bridged',
tagType: 'custom' as const,
ordinal: PREDEFINED_TAG_PRIORITY,
meta: { bgColor: bridgedTokenTagBgColor, textColor: bridgedTokenTagTextColor },
} :
undefined,
...formatUserTags(addressQuery.data),
verifiedInfoQuery.data?.projectSector ?
{ slug: verifiedInfoQuery.data.projectSector, name: verifiedInfoQuery.data.projectSector, tagType: 'custom' as const, ordinal: -30 } :
undefined,
...(addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags.filter(tag => tag.tagType !== 'note') || []),
].filter(Boolean).sort(sortEntityTags);
}, [
addressMetadataQuery.data?.addresses,
addressQuery.data,
bridgedTokenTagBgColor,
bridgedTokenTagTextColor,
tokenQuery.data,
verifiedInfoQuery.data?.projectSector,
hash,
multichainContext?.chain?.app_config,
]);
const contentAfter = (
<>
{ tokenQuery.data && <TokenEntity.Reputation value={ tokenQuery.data.reputation } ml={ 0 }/> }
{ verifiedInfoQuery.data?.tokenAddress && (
<Tooltip content={ `Information on this token has been verified by ${ config.chain.name }` }>
<IconSvg name="certified" color="green.500" boxSize={ 6 } cursor="pointer"/>
</Tooltip>
) }
<EntityTags
isLoading={ isLoading || (config.features.addressMetadata.isEnabled && addressMetadataQuery.isPending) }
tags={ tags }
addressHash={ addressQuery.data?.hash }
flexGrow={ 1 }
/>
</>
);
const secondRow = (
<Flex alignItems="center" w="100%" minW={ 0 } columnGap={ 2 } rowGap={ 2 } flexWrap={{ base: 'wrap', lg: 'nowrap' }}>
{ addressQuery.data && (
<AddressEntity
address={{ ...addressQuery.data, name: '' }}
isLoading={ isLoading }
variant="subheading"
icon={ multichainContext?.chain ? {
shield: { name: 'pie_chart', isLoading },
} : undefined }
/>
) }
{ !isLoading && tokenQuery.data && <AddressAddToWallet token={ tokenQuery.data } variant="button"/> }
{ addressQuery.data && <AddressQrCode hash={ addressQuery.data.hash } isLoading={ isLoading }/> }
<AccountActionsMenu isLoading={ isLoading }/>
<Flex ml={{ base: 0, lg: 'auto' }} columnGap={ 2 } flexGrow={{ base: 1, lg: 0 }}>
<TokenVerifiedInfo verifiedInfoQuery={ verifiedInfoQuery }/>
<NetworkExplorers type="token" pathParam={ addressHash } ml={{ base: 'auto', lg: 0 }}/>
</Flex>
</Flex>
);
return (
<>
<PageTitle
title={ `${ tokenQuery.data?.name || 'Unnamed token' }${ tokenSymbolText }` }
isLoading={ tokenQuery.isPlaceholderData }
beforeTitle={ tokenQuery.data ? (
<TokenEntity.Icon
token={ tokenQuery.data }
isLoading={ tokenQuery.isPlaceholderData }
variant="heading"
chain={ multichainContext?.chain }
/>
) : null }
contentAfter={ contentAfter }
secondRow={ secondRow }
/>
{ !addressMetadataQuery.isPending && (
<AddressAlerts
tags={ addressMetadataQuery.data?.addresses?.[hash.toLowerCase()]?.tags }
isScamToken={ tokenQuery.data?.reputation === 'scam' }
/>
) }
</>
);
};
export default TokenPageTitle;