mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
page base layout
This commit is contained in:
@@ -25,7 +25,7 @@ import type { RawTracesResponse } from 'types/api/rawTrace';
|
||||
import type { SearchResult, SearchResultFilters } from 'types/api/search';
|
||||
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
|
||||
import type { TokenCounters, TokenInfo, TokenHolders } from 'types/api/tokenInfo';
|
||||
import type { TokensResponse, TokensFilters } from 'types/api/tokens';
|
||||
import type { TokensResponse, TokensFilters, TokenInstance } from 'types/api/tokens';
|
||||
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
|
||||
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
|
||||
import type { TTxsFilters } from 'types/api/txsFilters';
|
||||
@@ -236,6 +236,11 @@ export const RESOURCES = {
|
||||
filterFields: [ 'filter' as const, 'type' as const ],
|
||||
},
|
||||
|
||||
// TOKEN INSTANCE
|
||||
token_instance: {
|
||||
path: '/api/v2/tokens/:hash/instances/:id',
|
||||
},
|
||||
|
||||
// HOMEPAGE
|
||||
homepage_stats: {
|
||||
path: '/api/v2/stats',
|
||||
@@ -357,6 +362,7 @@ Q extends 'token' ? TokenInfo :
|
||||
Q extends 'token_counters' ? TokenCounters :
|
||||
Q extends 'token_transfers' ? TokenTransferResponse :
|
||||
Q extends 'token_holders' ? TokenHolders :
|
||||
Q extends 'token_instance' ? TokenInstance :
|
||||
Q extends 'tokens' ? TokensResponse :
|
||||
Q extends 'search' ? SearchResult :
|
||||
Q extends 'contract' ? SmartContract :
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { NextPage } from 'next';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
import type { PageParams } from 'lib/next/token/types';
|
||||
|
||||
import getNetworkTitle from 'lib/networks/getNetworkTitle';
|
||||
import TokenInstance from 'ui/pages/TokenInstance';
|
||||
|
||||
const TokenInstancePage: NextPage<PageParams> = () => {
|
||||
const title = getNetworkTitle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{ title }</title>
|
||||
</Head>
|
||||
<TokenInstance/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenInstancePage;
|
||||
|
||||
export { getServerSideProps } from 'lib/next/token/getServerSideProps';
|
||||
@@ -1,14 +0,0 @@
|
||||
export type Tokenlist = {
|
||||
message: string;
|
||||
result: Array<TokenlistItem> | string;
|
||||
}
|
||||
|
||||
export type TokenlistItem = {
|
||||
balance: number;
|
||||
contractAddress: string;
|
||||
decimals: number | null;
|
||||
id: number;
|
||||
name: string;
|
||||
symbol: string;
|
||||
type: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { AddressParam } from './addressParams';
|
||||
import type { TokenInfo, TokenType } from './tokenInfo';
|
||||
|
||||
export type TokensResponse = {
|
||||
@@ -10,3 +11,15 @@ export type TokensResponse = {
|
||||
}
|
||||
|
||||
export type TokensFilters = { filter: string; type: Array<TokenType> | undefined };
|
||||
|
||||
export interface TokenInstance {
|
||||
is_unique: string;
|
||||
id: string;
|
||||
holder_address_hash: string | null;
|
||||
image_url: string | null;
|
||||
animation_url: string | null;
|
||||
external_app_url: string | null;
|
||||
metadata: unknown;
|
||||
owner: AddressParam;
|
||||
token: TokenInfo;
|
||||
}
|
||||
|
||||
@@ -9,25 +9,19 @@ import appConfig from 'configs/app/config';
|
||||
import blockIcon from 'icons/block.svg';
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import link from 'lib/link/link';
|
||||
import AddressCounterItem from 'ui/address/details/AddressCounterItem';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
|
||||
import HashStringShorten from 'ui/shared/HashStringShorten';
|
||||
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
|
||||
import LinkExternal from 'ui/shared/LinkExternal';
|
||||
import LinkInternal from 'ui/shared/LinkInternal';
|
||||
|
||||
import AddressAddToMetaMask from './details/AddressAddToMetaMask';
|
||||
import AddressBalance from './details/AddressBalance';
|
||||
import AddressDetailsSkeleton from './details/AddressDetailsSkeleton';
|
||||
import AddressFavoriteButton from './details/AddressFavoriteButton';
|
||||
import AddressNameInfo from './details/AddressNameInfo';
|
||||
import AddressQrCode from './details/AddressQrCode';
|
||||
import TokenSelect from './tokenSelect/TokenSelect';
|
||||
|
||||
interface Props {
|
||||
@@ -37,7 +31,6 @@ interface Props {
|
||||
|
||||
const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const addressHash = router.query.id?.toString();
|
||||
|
||||
@@ -92,18 +85,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems="center">
|
||||
<AddressIcon address={ data }/>
|
||||
<Text ml={ 2 } fontFamily="heading" fontWeight={ 500 }>
|
||||
{ isMobile ? <HashStringShorten hash={ data.hash }/> : data.hash }
|
||||
</Text>
|
||||
<CopyToClipboard text={ data.hash }/>
|
||||
{ data.is_contract && data.token && <AddressAddToMetaMask ml={ 2 } token={ data.token }/> }
|
||||
{ !data.is_contract && (
|
||||
<AddressFavoriteButton hash={ data.hash } isAdded={ Boolean(data.watchlist_names?.length) } ml={ 3 }/>
|
||||
) }
|
||||
<AddressQrCode hash={ data.hash } ml={ 2 }/>
|
||||
</Flex>
|
||||
<AddressHeadingInfo address={ data } token={ data.token } isLinkDisabled/>
|
||||
{ explorers.length > 0 && (
|
||||
<Flex mt={ 8 } columnGap={ 4 } flexWrap="wrap">
|
||||
<Text fontSize="sm">Verify with other explorers</Text>
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Skeleton, Box, Flex, SkeletonCircle, Tag } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import { useAppContext } from 'lib/appContext';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import AdBanner from 'ui/shared/ad/AdBanner';
|
||||
import TextAd from 'ui/shared/ad/TextAd';
|
||||
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
import Page from 'ui/shared/Page/Page';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
|
||||
import TokenLogo from 'ui/shared/TokenLogo';
|
||||
|
||||
export type TokenTabs = 'token_transfers' | 'holders'
|
||||
|
||||
const TokenInstance = () => {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
const appProps = useAppContext();
|
||||
|
||||
const hash = router.query.hash?.toString();
|
||||
const id = router.query.id?.toString();
|
||||
|
||||
const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`);
|
||||
|
||||
const scrollRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const tokenInstanceQuery = useApiQuery('token_instance', {
|
||||
pathParams: { hash, id },
|
||||
queryOptions: { enabled: Boolean(hash && id) },
|
||||
});
|
||||
|
||||
const tabs: Array<RoutedTab> = [
|
||||
{ id: 'token_transfers', title: 'Token transfers', component: <span>Token transfers</span> },
|
||||
{ id: 'holders', title: 'Holders', component: <span>Holders</span> },
|
||||
{ id: 'metadata', title: 'Metadata', component: <span>Metadata</span> },
|
||||
];
|
||||
|
||||
const content = (() => {
|
||||
if (tokenInstanceQuery.isError) {
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
if (tokenInstanceQuery.isLoading) {
|
||||
return (
|
||||
<Flex alignItems="center" mb={ 6 }>
|
||||
<SkeletonCircle w={ 6 } h={ 6 } mr={ 3 }/>
|
||||
<Skeleton w="500px" h={ 10 }/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const tokenLogo = <TokenLogo hash={ tokenInstanceQuery.data.token.address } name={ tokenInstanceQuery.data.token.name } boxSize={ 6 }/>;
|
||||
const tokenTag = <Tag>{ tokenInstanceQuery.data.token.type }</Tag>;
|
||||
const address = {
|
||||
hash: hash || '',
|
||||
is_contract: true,
|
||||
implementation_name: null,
|
||||
watchlist_names: [],
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextAd mb={ 6 }/>
|
||||
<PageTitle
|
||||
text={ `${ tokenInstanceQuery.data.token.name } #${ tokenInstanceQuery.data.id }` }
|
||||
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
|
||||
backLinkLabel="Back to token page"
|
||||
additionalsLeft={ tokenLogo }
|
||||
additionalsRight={ tokenTag }
|
||||
/>
|
||||
|
||||
<AddressHeadingInfo address={ address } token={ tokenInstanceQuery.data.token }/>
|
||||
|
||||
<AdBanner mt={{ base: 6, lg: 8 }} justifyContent="center"/>
|
||||
|
||||
{ /* should stay before tabs to scroll up with pagination */ }
|
||||
<Box ref={ scrollRef }></Box>
|
||||
|
||||
<RoutedTabs
|
||||
tabs={ tabs }
|
||||
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
<Page>{ content }</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokenInstance;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import type { AddressParam } from 'types/api/addressParams';
|
||||
import type { TokenInfo } from 'types/api/tokenInfo';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import AddressAddToMetaMask from 'ui/address/details/AddressAddToMetaMask';
|
||||
import AddressFavoriteButton from 'ui/address/details/AddressFavoriteButton';
|
||||
import AddressQrCode from 'ui/address/details/AddressQrCode';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
|
||||
interface Props {
|
||||
address: Pick<AddressParam, 'hash' | 'is_contract' | 'implementation_name' | 'watchlist_names'>;
|
||||
token?: TokenInfo | null;
|
||||
isLinkDisabled?: boolean;
|
||||
}
|
||||
|
||||
const AddressHeadingInfo = ({ address, token, isLinkDisabled }: Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<AddressIcon address={ address }/>
|
||||
<AddressLink
|
||||
type="address"
|
||||
hash={ address.hash }
|
||||
ml={ 2 }
|
||||
fontFamily="heading"
|
||||
fontWeight={ 500 }
|
||||
truncation={ isMobile ? 'constant' : 'none' }
|
||||
isDisabled={ isLinkDisabled }
|
||||
/>
|
||||
<CopyToClipboard text={ address.hash }/>
|
||||
{ address.is_contract && token && <AddressAddToMetaMask ml={ 2 } token={ token }/> }
|
||||
{ !address.is_contract && (
|
||||
<AddressFavoriteButton hash={ address.hash } isAdded={ Boolean(address.watchlist_names?.length) } ml={ 3 }/>
|
||||
) }
|
||||
<AddressQrCode hash={ address.hash } ml={ 2 }/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddressHeadingInfo;
|
||||
@@ -6,12 +6,7 @@ import React from 'react';
|
||||
import type { TokenInfo } from 'types/api/tokenInfo';
|
||||
|
||||
import useApiQuery from 'lib/api/useApiQuery';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import AddressAddToMetaMask from 'ui/address/details/AddressAddToMetaMask';
|
||||
import AddressQrCode from 'ui/address/details/AddressQrCode';
|
||||
import AddressContractIcon from 'ui/shared/address/AddressContractIcon';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
|
||||
|
||||
interface Props {
|
||||
tokenQuery: UseQueryResult<TokenInfo>;
|
||||
@@ -19,7 +14,6 @@ interface Props {
|
||||
|
||||
const TokenContractInfo = ({ tokenQuery }: Props) => {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const contractQuery = useApiQuery('address', {
|
||||
pathParams: { id: router.query.hash?.toString() },
|
||||
@@ -43,17 +37,14 @@ const TokenContractInfo = ({ tokenQuery }: Props) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hash = tokenQuery.data.address;
|
||||
const address = {
|
||||
hash: tokenQuery.data.address,
|
||||
is_contract: true,
|
||||
implementation_name: null,
|
||||
watchlist_names: [],
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<AddressContractIcon/>
|
||||
<AddressLink type="address" hash={ hash } ml={ 2 } truncation={ isMobile ? 'constant' : 'none' }/>
|
||||
<CopyToClipboard text={ hash } ml={ 1 }/>
|
||||
{ contractQuery.data?.token && <AddressAddToMetaMask token={ contractQuery.data?.token } ml={ 2 }/> }
|
||||
<AddressQrCode hash={ hash } ml={ 2 }/>
|
||||
</Flex>
|
||||
);
|
||||
return <AddressHeadingInfo address={ address } token={ contractQuery.data?.token }/>;
|
||||
};
|
||||
|
||||
export default React.memo(TokenContractInfo);
|
||||
|
||||
Reference in New Issue
Block a user