Commit 25305586 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into fix-ci-cd

parents 72f0706a c256c241
export default function hexToAddress(hex: string) {
const shortenHex = hex.slice(0, 66);
return shortenHex.slice(0, 2) + shortenHex.slice(26);
}
export default function hexToBytes(hex: string) {
const bytes = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substring(c, c + 2), 16));
}
return bytes;
}
import hexToBytes from 'lib/hexToBytes';
export default function hexToUtf8(hex: string) {
const utf8decoder = new TextDecoder();
const bytes = new Uint8Array(hexToBytes(hex));
return utf8decoder.decode(bytes);
}
...@@ -29,7 +29,7 @@ export default function useNavItems() { ...@@ -29,7 +29,7 @@ export default function useNavItems() {
return React.useMemo(() => { return React.useMemo(() => {
const mainNavItems = [ const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block') }, { text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block') },
{ text: 'Transactions', url: link('txs_validated'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') }, { text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' },
isMarketplaceFilled ? isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' } : null, { text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' } : null,
...@@ -41,7 +41,7 @@ export default function useNavItems() { ...@@ -41,7 +41,7 @@ export default function useNavItems() {
const accountNavItems = [ const accountNavItems = [
{ text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist' }, { text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist' },
{ text: 'Private tags', url: link('private_tags_address'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags') }, { text: 'Private tags', url: link('private_tags'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags') },
{ text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags' }, { text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags' },
{ text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys' }, { text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys' },
{ text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi' }, { text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi' },
......
export const ACCOUNT_ROUTES: Array<RouteName> = [ 'watchlist', 'private_tags_address', 'private_tags_tx', 'public_tags', 'api_keys', 'custom_abi' ]; export const ACCOUNT_ROUTES: Array<RouteName> = [ 'watchlist', 'private_tags', 'public_tags', 'api_keys', 'custom_abi' ];
import type { RouteName } from 'lib/link/routes'; import type { RouteName } from 'lib/link/routes';
export default function isAccountRoute(route: RouteName) { export default function isAccountRoute(route: RouteName) {
......
...@@ -20,12 +20,9 @@ export const ROUTES = { ...@@ -20,12 +20,9 @@ export const ROUTES = {
watchlist: { watchlist: {
pattern: `${ BASE_PATH }/account/watchlist`, pattern: `${ BASE_PATH }/account/watchlist`,
}, },
private_tags_address: { private_tags: {
pattern: `${ BASE_PATH }/account/tag_address`, pattern: `${ BASE_PATH }/account/tag_address`,
}, },
private_tags_tx: {
pattern: `${ BASE_PATH }/account/tag_transaction`,
},
public_tags: { public_tags: {
pattern: `${ BASE_PATH }/account/public_tags_request`, pattern: `${ BASE_PATH }/account/public_tags_request`,
}, },
...@@ -40,49 +37,22 @@ export const ROUTES = { ...@@ -40,49 +37,22 @@ export const ROUTES = {
}, },
// TRANSACTIONS // TRANSACTIONS
txs_validated: { txs: {
pattern: `${ BASE_PATH }/txs`, pattern: `${ BASE_PATH }/txs`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
txs_pending: { tx: {
pattern: `${ BASE_PATH }/pending-transactions`,
crossNetworkNavigation: true,
},
tx_index: {
pattern: `${ BASE_PATH }/tx/[id]`, pattern: `${ BASE_PATH }/tx/[id]`,
}, },
tx_internal: {
pattern: `${ BASE_PATH }/tx/[id]/internal-transactions`,
},
tx_logs: {
pattern: `${ BASE_PATH }/tx/[id]/logs`,
},
tx_raw_trace: {
pattern: `${ BASE_PATH }/tx/[id]/raw-trace`,
},
tx_state: {
pattern: `${ BASE_PATH }/tx/[id]/state`,
},
// BLOCKS // BLOCKS
blocks: { blocks: {
pattern: `${ BASE_PATH }/blocks`, pattern: `${ BASE_PATH }/blocks`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
blocks_uncles: { block: {
pattern: `${ BASE_PATH }/uncles`,
crossNetworkNavigation: true,
},
blocks_reorgs: {
pattern: `${ BASE_PATH }/reorgs`,
crossNetworkNavigation: true,
},
block_index: {
pattern: `${ BASE_PATH }/block/[id]`, pattern: `${ BASE_PATH }/block/[id]`,
}, },
block_txs: {
pattern: `${ BASE_PATH }/block/[id]/transactions`,
},
// TOKENS // TOKENS
tokens: { tokens: {
...@@ -99,6 +69,10 @@ export const ROUTES = { ...@@ -99,6 +69,10 @@ export const ROUTES = {
pattern: `${ BASE_PATH }/address/[id]`, pattern: `${ BASE_PATH }/address/[id]`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
address_contract_verification: {
pattern: `${ BASE_PATH }/address/[id]/contract_verifications/new`,
crossNetworkNavigation: true,
},
// APPS // APPS
apps: { apps: {
......
...@@ -5,16 +5,14 @@ import React from 'react'; ...@@ -5,16 +5,14 @@ import React from 'react';
import type { PageParams } from './types'; import type { PageParams } from './types';
import Block from 'ui/pages/Block'; import Block from 'ui/pages/Block';
import type { Props as BlockProps } from 'ui/pages/Block';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { type Props = {
pageParams: PageParams; pageParams: PageParams;
tab: BlockProps['tab'];
} }
const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { const BlockNextPage: NextPage<Props> = ({ pageParams }: Props) => {
const { title, description } = getSeo(pageParams); const { title, description } = getSeo(pageParams);
return ( return (
<> <>
...@@ -22,7 +20,7 @@ const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { ...@@ -22,7 +20,7 @@ const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Block tab={ tab }/> <Block/>
</> </>
); );
}; };
......
...@@ -2,26 +2,18 @@ import type { NextPage } from 'next'; ...@@ -2,26 +2,18 @@ import type { NextPage } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import type { PageParams } from './types';
import Blocks from 'ui/pages/Blocks'; import Blocks from 'ui/pages/Blocks';
import type { Props as BlocksProps } from 'ui/pages/Blocks';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { const BlocksNextPage: NextPage = () => {
pageParams: PageParams;
tab: BlocksProps['tab'];
}
const BlocksNextPage: NextPage<Props> = ({ tab }: Props) => {
const { title } = getSeo(); const { title } = getSeo();
return ( return (
<> <>
<Head> <Head>
<title>{ title }</title> <title>{ title }</title>
</Head> </Head>
<Blocks tab={ tab }/> <Blocks/>
</> </>
); );
}; };
......
...@@ -4,17 +4,15 @@ import React from 'react'; ...@@ -4,17 +4,15 @@ import React from 'react';
import type { PageParams } from './types'; import type { PageParams } from './types';
import type { Props as TransactionProps } from 'ui/pages/Transaction';
import Transaction from 'ui/pages/Transaction'; import Transaction from 'ui/pages/Transaction';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { type Props = {
pageParams: PageParams; pageParams: PageParams;
tab: TransactionProps['tab'];
} }
const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { const TransactionNextPage: NextPage<Props> = ({ pageParams }: Props) => {
const { title, description } = getSeo(pageParams); const { title, description } = getSeo(pageParams);
return ( return (
<> <>
...@@ -22,7 +20,7 @@ const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { ...@@ -22,7 +20,7 @@ const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Transaction tab={ tab }/> <Transaction/>
</> </>
); );
}; };
......
...@@ -19,7 +19,7 @@ const AddressTagsPage: NextPage<Props> = () => { ...@@ -19,7 +19,7 @@ const AddressTagsPage: NextPage<Props> = () => {
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
<PrivateTags tab="private_tags_address"/> <PrivateTags/>
</> </>
); );
}; };
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import PrivateTags from 'ui/pages/PrivateTags';
type PageParams = {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const TransactionTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle();
return (
<>
<Head><title>{ title }</title></Head>
<PrivateTags tab="private_tags_tx"/>
</>
);
};
export default TransactionTagsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -11,7 +11,7 @@ type Props = { ...@@ -11,7 +11,7 @@ type Props = {
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => { const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return ( return (
<BlockNextPage tab="block_txs" pageParams={ pageParams }/> <BlockNextPage pageParams={ pageParams }/>
); );
}; };
......
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlockNextPage from 'lib/next/block/BlockNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlockNextPage tab="block_index" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -9,9 +9,9 @@ type Props = { ...@@ -9,9 +9,9 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => { const BlockPage: NextPage<Props> = () => {
return ( return (
<BlocksNextPage tab="blocks" pageParams={ pageParams }/> <BlocksNextPage/>
); );
}; };
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Transactions from 'ui/pages/Transactions';
type PageParams = {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const AddressTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle();
return (
<>
<Head><title>{ title }</title></Head>
<Transactions tab="txs_pending"/>
</>
);
};
export default AddressTagsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlocksNextPage from 'lib/next/blocks/BlocksNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlocksNextPage tab="blocks_reorgs" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -11,7 +11,7 @@ type Props = { ...@@ -11,7 +11,7 @@ type Props = {
const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => { const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
return ( return (
<TransactionNextPage tab="tx_index" pageParams={ pageParams }/> <TransactionNextPage pageParams={ pageParams }/>
); );
}; };
......
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import TransactionNextPage from 'lib/next/tx/TransactionNextPage';
type Props = {
pageParams: PageParams;
}
const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
return <TransactionNextPage pageParams={ pageParams } tab="tx_internal"/>;
};
export default TransactionPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import TransactionNextPage from 'lib/next/tx/TransactionNextPage';
type Props = {
pageParams: PageParams;
}
const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
return <TransactionNextPage pageParams={ pageParams } tab="tx_logs"/>;
};
export default TransactionPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import TransactionNextPage from 'lib/next/tx/TransactionNextPage';
type Props = {
pageParams: PageParams;
}
const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
return <TransactionNextPage pageParams={ pageParams } tab="tx_raw_trace"/>;
};
export default TransactionPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import TransactionNextPage from 'lib/next/tx/TransactionNextPage';
type Props = {
pageParams: PageParams;
}
const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
return <TransactionNextPage pageParams={ pageParams } tab="tx_state"/>;
};
export default TransactionPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -19,7 +19,7 @@ const AddressTagsPage: NextPage<Props> = () => { ...@@ -19,7 +19,7 @@ const AddressTagsPage: NextPage<Props> = () => {
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
<Transactions tab="txs_validated"/> <Transactions/>
</> </>
); );
}; };
......
import type { NextPage } from 'next';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlocksNextPage from 'lib/next/blocks/BlocksNextPage';
type Props = {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
return (
<BlocksNextPage tab="blocks_uncles" pageParams={ pageParams }/>
);
};
export default BlockPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -62,7 +62,7 @@ const BlockDetails = () => { ...@@ -62,7 +62,7 @@ const BlockDetails = () => {
title="Transactions" title="Transactions"
hint="The number of transactions in the block." hint="The number of transactions in the block."
> >
<Link href={ link('block_txs', { id: router.query.id }) }> <Link href={ link('block', { id: router.query.id }, { tab: 'transactions' }) }>
{ block.transactionsNum } transactions { block.transactionsNum } transactions
</Link> </Link>
</DetailsInfoItem> </DetailsInfoItem>
......
...@@ -29,7 +29,7 @@ const BlocksListItem = ({ data, isPending }: Props) => { ...@@ -29,7 +29,7 @@ const BlocksListItem = ({ data, isPending }: Props) => {
{ isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> } { isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> }
<Link <Link
fontWeight={ 600 } fontWeight={ 600 }
href={ link('block_index', { id: String(data.height) }) } href={ link('block', { id: String(data.height) }) }
> >
{ data.height } { data.height }
</Link> </Link>
......
...@@ -28,7 +28,7 @@ const BlocksTableItem = ({ data, isPending }: Props) => { ...@@ -28,7 +28,7 @@ const BlocksTableItem = ({ data, isPending }: Props) => {
{ isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> } { isPending && <Spinner size="sm" color="blue.500" emptyColor={ spinnerEmptyColor }/> }
<Link <Link
fontWeight={ 600 } fontWeight={ 600 }
href={ link('block_index', { id: String(data.height) }) } href={ link('block', { id: String(data.height) }) }
> >
{ data.height } { data.height }
</Link> </Link>
......
...@@ -10,15 +10,11 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -10,15 +10,11 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
{ routeName: 'block_index', title: 'Details', component: <BlockDetails/> }, { id: 'index', title: 'Details', component: <BlockDetails/> },
{ routeName: 'block_txs', title: 'Transactions', component: <BlockTxs/> }, { id: 'txs', title: 'Transactions', component: <BlockTxs/> },
]; ];
export interface Props { const BlockPageContent = () => {
tab: RoutedTab['routeName'];
}
const BlockPageContent = ({ tab }: Props) => {
const router = useRouter(); const router = useRouter();
return ( return (
...@@ -26,7 +22,6 @@ const BlockPageContent = ({ tab }: Props) => { ...@@ -26,7 +22,6 @@ const BlockPageContent = ({ tab }: Props) => {
<PageTitle text={ `Block #${ router.query.id || '' }` }/> <PageTitle text={ `Block #${ router.query.id || '' }` }/>
<RoutedTabs <RoutedTabs
tabs={ TABS } tabs={ TABS }
defaultActiveTab={ tab }
/> />
</Page> </Page>
); );
......
...@@ -8,22 +8,17 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -8,22 +8,17 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
{ routeName: 'blocks', title: 'All', component: <BlocksContent/> }, { id: 'blocks', title: 'All', component: <BlocksContent/> },
{ routeName: 'blocks_reorgs', title: 'Forked', component: <BlocksContent/> }, { id: 'reorgs', title: 'Forked', component: <BlocksContent/> },
{ routeName: 'blocks_uncles', title: 'Uncles', component: <BlocksContent/> }, { id: 'uncles', title: 'Uncles', component: <BlocksContent/> },
]; ];
export interface Props { const BlocksPageContent = () => {
tab: RoutedTab['routeName'];
}
const BlocksPageContent = ({ tab }: Props) => {
return ( return (
<Page> <Page>
<PageTitle text="Blocks"/> <PageTitle text="Blocks"/>
<RoutedTabs <RoutedTabs
tabs={ TABS } tabs={ TABS }
defaultActiveTab={ tab }
/> />
</Page> </Page>
); );
......
...@@ -9,19 +9,15 @@ import PageTitle from 'ui/shared/Page/PageTitle'; ...@@ -9,19 +9,15 @@ import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
{ routeName: 'private_tags_address', title: 'Address', component: <PrivateAddressTags/> }, { id: 'address', title: 'Address', component: <PrivateAddressTags/> },
{ routeName: 'private_tags_tx', title: 'Transaction', component: <PrivateTransactionTags/> }, { id: 'tx', title: 'Transaction', component: <PrivateTransactionTags/> },
]; ];
type Props = { const PrivateTags = () => {
tab: RoutedTab['routeName'];
}
const PrivateTags = ({ tab }: Props) => {
return ( return (
<Page> <Page>
<PageTitle text="Private tags"/> <PageTitle text="Private tags"/>
<RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/> <RoutedTabs tabs={ TABS }/>
</Page> </Page>
); );
}; };
......
...@@ -16,25 +16,21 @@ import TxRawTrace from 'ui/tx/TxRawTrace'; ...@@ -16,25 +16,21 @@ import TxRawTrace from 'ui/tx/TxRawTrace';
// import TxState from 'ui/tx/TxState'; // import TxState from 'ui/tx/TxState';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
{ routeName: 'tx_index', title: 'Details', component: <TxDetails/> }, { id: 'index', title: 'Details', component: <TxDetails/> },
{ routeName: 'tx_internal', title: 'Internal txn', component: <TxInternals/> }, { id: 'internal', title: 'Internal txn', component: <TxInternals/> },
{ routeName: 'tx_logs', title: 'Logs', component: <TxLogs/> }, { id: 'logs', title: 'Logs', component: <TxLogs/> },
// will be implemented later, api is not ready // will be implemented later, api is not ready
// { routeName: 'tx_state', title: 'State', component: <TxState/> }, // { id: 'state', title: 'State', component: <TxState/> },
{ routeName: 'tx_raw_trace', title: 'Raw trace', component: <TxRawTrace/> }, { id: 'raw_trace', title: 'Raw trace', component: <TxRawTrace/> },
]; ];
export interface Props { const TransactionPageContent = () => {
tab: RoutedTab['routeName'];
}
const TransactionPageContent = ({ tab }: Props) => {
const link = useLink(); const link = useLink();
return ( return (
<Page> <Page>
{ /* TODO should be shown only when navigating from txs list */ } { /* TODO should be shown only when navigating from txs list */ }
<Link mb={ 6 } display="inline-flex" href={ link('txs_validated') }> <Link mb={ 6 } display="inline-flex" href={ link('txs') }>
<Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/> <Icon as={ eastArrowIcon } boxSize={ 6 } mr={ 2 } transform="rotate(180deg)"/>
Transactions Transactions
</Link> </Link>
...@@ -56,7 +52,6 @@ const TransactionPageContent = ({ tab }: Props) => { ...@@ -56,7 +52,6 @@ const TransactionPageContent = ({ tab }: Props) => {
</Flex> </Flex>
<RoutedTabs <RoutedTabs
tabs={ TABS } tabs={ TABS }
defaultActiveTab={ tab }
/> />
</Page> </Page>
); );
......
...@@ -12,21 +12,17 @@ import TxsPending from 'ui/txs/TxsPending'; ...@@ -12,21 +12,17 @@ import TxsPending from 'ui/txs/TxsPending';
import TxsValidated from 'ui/txs/TxsValidated'; import TxsValidated from 'ui/txs/TxsValidated';
const TABS: Array<RoutedTab> = [ const TABS: Array<RoutedTab> = [
{ routeName: 'txs_validated', title: 'Validated', component: <TxsValidated/> }, { id: 'validated', title: 'Validated', component: <TxsValidated/> },
{ routeName: 'txs_pending', title: 'Pending', component: <TxsPending/> }, { id: 'pending', title: 'Pending', component: <TxsPending/> },
]; ];
type Props = { const Transactions = () => {
tab: RoutedTab['routeName'];
}
const Transactions = ({ tab }: Props) => {
return ( return (
<Page> <Page>
<Box h="100%"> <Box h="100%">
<PageTitle text="Transactions"/> <PageTitle text="Transactions"/>
<RoutedTabs tabs={ TABS } defaultActiveTab={ tab }/> <RoutedTabs tabs={ TABS }/>
</Box> </Box>
</Page> </Page>
); );
......
import { Box, Flex, Select, Textarea } from '@chakra-ui/react'; import { Box, Flex, Select, Textarea } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import hexToUtf8 from 'lib/hexToUtf8';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
type DataType = 'Hex' | 'UTF-8' type DataType = 'Hex' | 'UTF-8'
...@@ -26,7 +27,7 @@ const RawInputData = ({ hex }: Props) => { ...@@ -26,7 +27,7 @@ const RawInputData = ({ hex }: Props) => {
<CopyToClipboard text={ hex }/> <CopyToClipboard text={ hex }/>
</Flex> </Flex>
<Textarea <Textarea
value={ selectedDataType === 'Hex' ? hex : 'UTF-8 equivalent' } value={ selectedDataType === 'Hex' ? hex : hexToUtf8(hex) }
w="100%" w="100%"
maxH="220px" maxH="220px"
mt={ 2 } mt={ 2 }
...@@ -38,4 +39,4 @@ const RawInputData = ({ hex }: Props) => { ...@@ -38,4 +39,4 @@ const RawInputData = ({ hex }: Props) => {
); );
}; };
export default RawInputData; export default React.memo(RawInputData);
...@@ -7,12 +7,11 @@ import { ...@@ -7,12 +7,11 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/styled-system'; import type { StyleProps } from '@chakra-ui/styled-system';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React, { useEffect, useState } from 'react';
import type { RoutedTab } from './types'; import type { RoutedTab } from './types';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { link } from 'lib/link/link';
import RoutedTabsMenu from './RoutedTabsMenu'; import RoutedTabsMenu from './RoutedTabsMenu';
import useAdaptiveTabs from './useAdaptiveTabs'; import useAdaptiveTabs from './useAdaptiveTabs';
...@@ -26,29 +25,36 @@ const hiddenItemStyles: StyleProps = { ...@@ -26,29 +25,36 @@ const hiddenItemStyles: StyleProps = {
interface Props { interface Props {
tabs: Array<RoutedTab>; tabs: Array<RoutedTab>;
defaultActiveTab: RoutedTab['routeName'];
} }
const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => { const RoutedTabs = ({ tabs }: Props) => {
const defaultIndex = tabs.findIndex(({ routeName }) => routeName === defaultActiveTab); const router = useRouter();
const isMobile = useIsMobile(); const [ activeTabIndex, setActiveTabIndex ] = useState<number>(tabs.length + 1);
useEffect(() => {
if (router.isReady) {
let tabIndex = 0;
if (router.query.tab) {
tabIndex = tabs.findIndex(({ id }) => id === router.query.tab);
if (tabIndex < 0) {
tabIndex = 0;
}
}
setActiveTabIndex(tabIndex);
}
}, [ tabs, router ]);
const [ activeTab ] = React.useState<number>(defaultIndex); const isMobile = useIsMobile();
const { tabsCut, tabsList, tabsRefs, listRef } = useAdaptiveTabs(tabs, isMobile); const { tabsCut, tabsList, tabsRefs, listRef } = useAdaptiveTabs(tabs, isMobile);
const router = useRouter();
const handleTabChange = React.useCallback((index: number) => { const handleTabChange = React.useCallback((index: number) => {
const nextTab = tabs[index]; const nextTab = tabs[index];
if (nextTab.routeName) { router.query.tab = nextTab.id;
const newUrl = link(nextTab.routeName, router.query); router.push(router);
router.push(newUrl, undefined, { shallow: true });
}
}, [ tabs, router ]); }, [ tabs, router ]);
return ( return (
<Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ handleTabChange } index={ activeTab }> <Tabs variant="soft-rounded" colorScheme="blue" isLazy onChange={ handleTabChange } index={ activeTabIndex }>
<TabList <TabList
marginBottom={{ base: 6, lg: 12 }} marginBottom={{ base: 6, lg: 12 }}
flexWrap="nowrap" flexWrap="nowrap"
...@@ -68,14 +74,14 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => { ...@@ -68,14 +74,14 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => {
}} }}
> >
{ tabsList.map((tab, index) => { { tabsList.map((tab, index) => {
if (!tab.routeName) { if (!tab.id) {
return ( return (
<RoutedTabsMenu <RoutedTabsMenu
key="menu" key="menu"
tabs={ tabs } tabs={ tabs }
activeTab={ tabs[activeTab] } activeTab={ tabs[activeTabIndex] }
tabsCut={ tabsCut } tabsCut={ tabsCut }
isActive={ activeTab >= tabsCut } isActive={ activeTabIndex >= tabsCut }
styles={ tabsCut < tabs.length ? styles={ tabsCut < tabs.length ?
// initially our cut is 0 and we don't want to show the menu button too // initially our cut is 0 and we don't want to show the menu button too
// but we want to keep it in the tabs row so it won't collapse // but we want to keep it in the tabs row so it won't collapse
...@@ -91,7 +97,7 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => { ...@@ -91,7 +97,7 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => {
return ( return (
<Tab <Tab
key={ tab.routeName } key={ tab.id }
ref={ tabsRefs[index] } ref={ tabsRefs[index] }
{ ...(index < tabsCut ? {} : hiddenItemStyles) } { ...(index < tabsCut ? {} : hiddenItemStyles) }
scrollSnapAlign="start" scrollSnapAlign="start"
...@@ -102,7 +108,7 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => { ...@@ -102,7 +108,7 @@ const RoutedTabs = ({ tabs, defaultActiveTab }: Props) => {
}) } }) }
</TabList> </TabList>
<TabPanels> <TabPanels>
{ tabsList.map((tab) => <TabPanel padding={ 0 } key={ tab.routeName }>{ tab.component }</TabPanel>) } { tabsList.map((tab) => <TabPanel padding={ 0 } key={ tab.id }>{ tab.component }</TabPanel>) }
</TabPanels> </TabPanels>
</Tabs> </Tabs>
); );
......
...@@ -49,10 +49,10 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe ...@@ -49,10 +49,10 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe
<PopoverBody display="flex" flexDir="column"> <PopoverBody display="flex" flexDir="column">
{ tabs.slice(tabsCut).map((tab, index) => ( { tabs.slice(tabsCut).map((tab, index) => (
<Button <Button
key={ tab.routeName } key={ tab.id }
variant="ghost" variant="ghost"
onClick={ handleItemClick } onClick={ handleItemClick }
isActive={ activeTab.routeName === tab.routeName } isActive={ activeTab.id === tab.id }
justifyContent="left" justifyContent="left"
data-index={ index } data-index={ index }
> >
......
import type { RouteName } from 'lib/link/routes';
export interface RoutedTab { export interface RoutedTab {
// for simplicity we use routeName as an id id: string;
// if we migrate to non-Next.js router that should be revised
// id: string;
routeName: RouteName | null;
title: string; title: string;
component: React.ReactNode; component: React.ReactNode;
} }
export interface MenuButton { export interface MenuButton {
routeName: null; id: null;
title: string; title: string;
component: null; component: null;
} }
...@@ -3,7 +3,7 @@ import type { MenuButton } from './types'; ...@@ -3,7 +3,7 @@ import type { MenuButton } from './types';
import { middot } from 'lib/html-entities'; import { middot } from 'lib/html-entities';
export const menuButton: MenuButton = { export const menuButton: MenuButton = {
routeName: null, id: null,
title: `${ middot }${ middot }${ middot }`, title: `${ middot }${ middot }${ middot }`,
component: null, component: null,
}; };
...@@ -19,11 +19,11 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, ...@@ -19,11 +19,11 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
const link = useLink(); const link = useLink();
let url; let url;
if (type === 'transaction') { if (type === 'transaction') {
url = link('tx_index', { id: id || hash }); url = link('tx', { id: id || hash });
} else if (type === 'token') { } else if (type === 'token') {
url = link('token_index', { id: id || hash }); url = link('token_index', { id: id || hash });
} else if (type === 'block') { } else if (type === 'block') {
url = link('block_index', { id: id || hash }); url = link('block', { id: id || hash });
} else { } else {
url = link('address_index', { id: id || hash }); url = link('address_index', { id: id || hash });
} }
......
import { Text, Grid, GridItem, Link, Tooltip, Button, Icon, useColorModeValue } from '@chakra-ui/react'; import { Text, Grid, GridItem, Tooltip, Button, useColorModeValue, Alert, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Log } from 'types/api/log'; import type { Log } from 'types/api/log';
import searchIcon from 'icons/search.svg'; // import searchIcon from 'icons/search.svg';
import { space } from 'lib/html-entities';
import useLink from 'lib/link/useLink';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -22,6 +24,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -22,6 +24,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const link = useLink();
return ( return (
<Grid <Grid
...@@ -36,17 +39,26 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -36,17 +39,26 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
pt: 0, pt: 0,
}} }}
> >
{ !decoded && (
<GridItem colSpan={{ base: 1, lg: 2 }}>
<Alert status="warning" display="inline-table" whiteSpace="normal">
To see accurate decoded input data, the contract must be verified.{ space }
<Link href={ link('address_contract_verification', { id: address.hash }) }>Verify the contract here</Link>
</Alert>
</GridItem>
) }
<RowHeader>Address</RowHeader> <RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address> <Address mr={{ base: 9, lg: 0 }}>
<AddressIcon hash={ address.hash }/> <AddressIcon hash={ address.hash }/>
<AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/> <AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address> </Address>
<Tooltip label="Find matches topic"> { /* api doesn't have find topic feature yet */ }
{ /* <Tooltip label="Find matches topic">
<Link ml={ 2 } mr={{ base: 9, lg: 0 }} display="inline-flex"> <Link ml={ 2 } mr={{ base: 9, lg: 0 }} display="inline-flex">
<Icon as={ searchIcon } boxSize={ 5 }/> <Icon as={ searchIcon } boxSize={ 5 }/>
</Link> </Link>
</Tooltip> </Tooltip> */ }
<Tooltip label="Log index"> <Tooltip label="Log index">
<Button variant="outline" colorScheme="gray" isActive ml="auto" size="sm" fontWeight={ 400 }> <Button variant="outline" colorScheme="gray" isActive ml="auto" size="sm" fontWeight={ 400 }>
{ index } { index }
...@@ -63,7 +75,13 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -63,7 +75,13 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
) } ) }
<RowHeader>Topics</RowHeader> <RowHeader>Topics</RowHeader>
<GridItem> <GridItem>
{ topics.filter(Boolean).map((item, index) => <TxLogTopic key={ index } hex={ item } index={ index }/>) } { topics.filter(Boolean).map((item, index) => (
<TxLogTopic
key={ index }
hex={ item }
index={ index }
/>
)) }
</GridItem> </GridItem>
<RowHeader>Data</RowHeader> <RowHeader>Data</RowHeader>
<GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }> <GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }>
......
import { Flex, Button, Select, Box } from '@chakra-ui/react'; import { Flex, Button, Select, Box } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react'; import React from 'react';
import hexToAddress from 'lib/hexToAddress';
import hexToUtf8 from 'lib/hexToUtf8';
import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props { interface Props {
...@@ -8,36 +14,71 @@ interface Props { ...@@ -8,36 +14,71 @@ interface Props {
index: number; index: number;
} }
type DataType = 'Hex' | 'Dec' type DataType = 'hex' | 'text' | 'address' | 'number';
const OPTIONS: Array<DataType> = [ 'Hex', 'Dec' ];
const VALUE_CONVERTERS: Record<DataType, (hex: string) => string> = {
hex: (hex) => hex,
text: hexToUtf8,
address: hexToAddress,
number: (hex) => BigInt(hex).toString(),
};
const OPTIONS: Array<DataType> = [ 'hex', 'address', 'text', 'number' ];
const TxLogTopic = ({ hex, index }: Props) => { const TxLogTopic = ({ hex, index }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('Hex'); const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('hex');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => { const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType); setSelectedDataType(event.target.value as DataType);
}, []); }, []);
const value = VALUE_CONVERTERS[selectedDataType.toLowerCase() as Lowercase<DataType>](hex);
const content = (() => {
switch (selectedDataType) {
case 'hex':
case 'number':
case 'text': {
return (
<>
<Box overflow="hidden" whiteSpace="nowrap">
<HashStringShortenDynamic hash={ value }/>
</Box>
<CopyToClipboard text={ value }/>
</>
);
}
case 'address': {
return (
<Address>
<AddressLink hash={ value }/>
<CopyToClipboard text={ value }/>
</Address>
);
}
}
})();
return ( return (
<Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%"> <Flex alignItems="center" px={{ base: 0, lg: 3 }} _notFirst={{ mt: 3 }} overflow="hidden" maxW="100%">
<Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }> <Button variant="outline" colorScheme="gray" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }>
{ index } { index }
</Button> </Button>
<Select { index !== 0 && (
size="sm" <Select
borderRadius="base" size="sm"
value={ selectedDataType } borderRadius="base"
onChange={ handleSelectChange } value={ selectedDataType }
focusBorderColor="none" onChange={ handleSelectChange }
w="75px" focusBorderColor="none"
mr={ 3 } mr={ 3 }
flexShrink={ 0 } flexShrink={ 0 }
> w="auto"
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) } >
</Select> { OPTIONS.map((option) => <option key={ option } value={ option }>{ capitalize(option) }</option>) }
<Box overflow="hidden" whiteSpace="nowrap"> </Select>
<HashStringShortenDynamic hash={ hex }/> ) }
</Box> { content }
</Flex> </Flex>
); );
}; };
......
...@@ -77,7 +77,7 @@ const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => { ...@@ -77,7 +77,7 @@ const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => {
<Text fontWeight="600" as="span">{ tx.position }</Text> <Text fontWeight="600" as="span">{ tx.position }</Text>
</Box> </Box>
</Box> </Box>
<Link fontSize="sm" href={ link('tx_index', { id: tx.hash }) }>More details</Link> <Link fontSize="sm" href={ link('tx', { id: tx.hash }) }>More details</Link>
</> </>
); );
}; };
......
...@@ -78,7 +78,7 @@ const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => { ...@@ -78,7 +78,7 @@ const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
</Flex> </Flex>
<Box mt={ 2 }> <Box mt={ 2 }>
<Text as="span">Block </Text> <Text as="span">Block </Text>
<Link href={ link('block_index', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link> <Link href={ link('block', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
</Box> </Box>
<Flex alignItems="center" height={ 6 } mt={ 6 }> <Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address width="calc((100%-40px)/2)"> <Address width="calc((100%-40px)/2)">
......
...@@ -104,7 +104,7 @@ const TxsTableItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => { ...@@ -104,7 +104,7 @@ const TxsTableItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
</TruncatedTextTooltip> </TruncatedTextTooltip>
</Td> </Td>
<Td> <Td>
<Link href={ link('block_index', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link> <Link href={ link('block', { id: tx.block_num.toString() }) }>{ tx.block_num }</Link>
</Td> </Td>
<Show above="xl"> <Show above="xl">
<Td> <Td>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment