Commit fd1e8693 authored by tom's avatar tom

tx routes

parent 058a6dd7
......@@ -115,25 +115,25 @@ export const RESOURCES = {
filterFields: [ 'filter' as const, 'type' as const, 'method' as const ],
},
tx: {
path: '/api/v2/transactions/:id',
path: '/api/v2/transactions/:hash',
},
tx_internal_txs: {
path: '/api/v2/transactions/:id/internal-transactions',
path: '/api/v2/transactions/:hash/internal-transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'transaction_hash' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ ],
},
tx_logs: {
path: '/api/v2/transactions/:id/logs',
path: '/api/v2/transactions/:hash/logs',
paginationFields: [ 'items_count' as const, 'transaction_hash' as const, 'index' as const ],
filterFields: [ ],
},
tx_token_transfers: {
path: '/api/v2/transactions/:id/token-transfers',
path: '/api/v2/transactions/:hash/token-transfers',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'transaction_hash' as const, 'index' as const ],
filterFields: [ 'type' as const ],
},
tx_raw_trace: {
path: '/api/v2/transactions/:id/raw-trace',
path: '/api/v2/transactions/:hash/raw-trace',
},
// ADDRESSES
......
......@@ -12,8 +12,8 @@ it('makes correct link if there are params in path', () => {
expect(result).toBe('https://blockscout.com/token/0x67e90a54AeEA85f21949c645082FE95d77BC1E70/instance/42');
});
it('makes correct link with query params', () => {
const result = link('tx', { id: '0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2' }, { tab: 'index', foo: 'bar' });
// it('makes correct link with query params', () => {
// const result = link('tx', { id: '0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2' }, { tab: 'index', foo: 'bar' });
expect(result).toBe('https://blockscout.com/tx/0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2?tab=index&foo=bar');
});
// expect(result).toBe('https://blockscout.com/tx/0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2?tab=index&foo=bar');
// });
......@@ -38,10 +38,12 @@ export const ROUTES = {
// },
// TRANSACTIONS
txs: {
pattern: PATHS.txs,
crossNetworkNavigation: true,
},
// txs: {
// pattern: PATHS.txs,
// crossNetworkNavigation: true,
// },
// todo_tom need full url builder
tx: {
pattern: PATHS.tx,
},
......
......@@ -5,6 +5,7 @@ export type Props = {
referrer: string;
id?: string;
height?: string;
hash?: string;
}
export const getServerSideProps: GetServerSideProps<Props> = async({ req, query }) => {
......@@ -14,6 +15,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, query
referrer: req.headers.referer || '',
id: query.id?.toString() || '',
height: query.height?.toString() || '',
hash: query.hash?.toString() || '',
},
};
};
import type { PageParams } from './types';
import type { RoutedQuery } from 'nextjs-routes';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params?: PageParams) {
export default function getSeo(params?: RoutedQuery<'/tx/[hash]'>) {
const networkTitle = getNetworkTitle();
return {
title: params ? `Transaction ${ params.id } - ${ networkTitle }` : '',
description: params ? `View transaction ${ params.id } on ${ networkTitle }` : '',
title: params ? `Transaction ${ params.hash } - ${ networkTitle }` : '',
description: params ? `View transaction ${ params.hash } on ${ networkTitle }` : '',
};
}
export type PageParams = {
id: string;
}
......@@ -50,7 +50,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, r
return link('address_index', { id: payload.parameter || q });
}
case 'transaction': {
return link('tx', { id: q });
return route({ pathname: '/tx/[hash]', query: { hash: q } });
}
}
})();
......
import type { NextPage } from 'next';
import Head from 'next/head';
import type { RoutedQuery } from 'nextjs-routes';
import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import getSeo from 'lib/next/tx/getSeo';
import Transaction from 'ui/pages/Transaction';
const TransactionPage: NextPage<PageParams> = ({ id }: PageParams) => {
const { title, description } = getSeo({ id });
const TransactionPage: NextPage<RoutedQuery<'/tx/[hash]'>> = ({ hash }: RoutedQuery<'/tx/[hash]'>) => {
const { title, description } = getSeo({ hash });
return (
<>
......
......@@ -28,7 +28,7 @@ declare module "nextjs-routes" {
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| StaticRoute<"/tokens">
| DynamicRoute<"/tx/[id]", { "id": string }>
| DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs">
| StaticRoute<"/visualize/sol2uml">;
......
import { Box, chakra, Spinner } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { ContractMethodWriteResult } from './types';
import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
......@@ -30,9 +30,9 @@ const ContractWriteResultDumb = ({ result, onSettle, txInfo }: Props) => {
const isErrorResult = 'message' in result;
const txLink = (
<LinkInternal href={ link('tx', { id: txHash }) }>View transaction details</LinkInternal>
);
const txLink = txHash ? (
<LinkInternal href={ route({ pathname: '/tx/[hash]', query: { hash: txHash } }) }>View transaction details</LinkInternal>
) : null;
const content = (() => {
if (isErrorResult) {
......
......@@ -117,7 +117,7 @@ const BlockDetails = () => {
title="Transactions"
hint="The number of transactions in the block."
>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: router.query.height?.toString() || '', tab: 'txs' } }) }>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height, tab: 'txs' } }) }>
{ data.tx_count } transactions
</LinkInternal>
</DetailsInfoItem>
......
import { Box, Heading, Flex, Text, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useNewTxsSocket from 'lib/hooks/useNewTxsSocket';
import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
import SocketNewItemsNotice from 'ui/shared/SocketNewItemsNotice';
......@@ -34,7 +34,7 @@ const LatestTransactions = () => {
}
if (data) {
const txsUrl = link('txs');
const txsUrl = route({ pathname: '/txs' });
content = (
<>
<SocketNewItemsNotice borderBottomRadius={ 0 } url={ txsUrl } num={ num } alert={ socketAlert }/>
......
......@@ -9,7 +9,6 @@ import gasIcon from 'icons/gas.svg';
import txIcon from 'icons/transactions.svg';
import walletIcon from 'icons/wallet.svg';
import useApiQuery from 'lib/api/useApiQuery';
import link from 'lib/link/link';
import StatsGasPrices from './StatsGasPrices';
import StatsItem from './StatsItem';
......@@ -59,7 +58,7 @@ const Stats = () => {
icon={ txIcon }
title="Total transactions"
value={ Number(data.total_transactions).toLocaleString() }
url={ link('txs') }
url={ route({ pathname: '/txs' }) }
/>
<StatsItem
icon={ walletIcon }
......
......@@ -7,6 +7,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import networkExplorers from 'lib/networks/networkExplorers';
import getQueryParamString from 'lib/router/getQueryParamString';
import TextAd from 'ui/shared/ad/TextAd';
import LinkExternal from 'ui/shared/LinkExternal';
import Page from 'ui/shared/Page/Page';
......@@ -34,16 +35,17 @@ const TransactionPageContent = () => {
const appProps = useAppContext();
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/txs');
const hash = getQueryParamString(router.query.hash);
const { data } = useApiQuery('tx', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
pathParams: { hash },
queryOptions: { enabled: Boolean(hash) },
});
const explorersLinks = networkExplorers
.filter((explorer) => explorer.paths.tx)
.map((explorer) => {
const url = new URL(explorer.paths.tx + '/' + router.query.id, explorer.baseUrl);
const url = new URL(explorer.paths.tx + '/' + hash, explorer.baseUrl);
return <LinkExternal key={ explorer.baseUrl } title={ `Open in ${ explorer.title }` } href={ url.toString() }/>;
});
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { ROUTES } from 'lib/link/routes';
import TestApp from 'playwright/TestApp';
import SocketNewItemsNotice from './SocketNewItemsNotice';
const hooksConfig = {
router: {
pathname: ROUTES.txs.pattern,
pathname: '/tx/[hash]',
query: {},
},
};
......
......@@ -45,7 +45,7 @@ const AddressLink = (props: Props) => {
let url;
if (type === 'transaction') {
url = link('tx', { id: hash });
url = route({ pathname: '/tx/[hash]', query: { hash } });
} else if (type === 'token') {
url = link('token_index', { hash: hash });
} else if (type === 'block') {
......
......@@ -30,7 +30,7 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm }: Props) => {
return link('address_index', { id: data.address });
}
case 'transaction': {
return link('tx', { id: data.tx_hash });
return route({ pathname: '/tx/[hash]', query: { hash: data.tx_hash } });
}
case 'block': {
return route({ pathname: '/block/[height]', query: { height: String(data.block_number) } });
......
......@@ -8,10 +8,10 @@ import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
import TxDetails from './TxDetails';
const API_URL = buildApiUrl('tx', { id: '1' });
const API_URL = buildApiUrl('tx', { hash: '1' });
const hooksConfig = {
router: {
query: { id: 1 },
query: { hash: 1 },
},
};
......
......@@ -9,11 +9,11 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import TxInternals from './TxInternals';
const TX_HASH = txMock.base.hash;
const API_URL_TX = buildApiUrl('tx', { id: TX_HASH });
const API_URL_TX_INTERNALS = buildApiUrl('tx_internal_txs', { id: TX_HASH });
const API_URL_TX = buildApiUrl('tx', { hash: TX_HASH });
const API_URL_TX_INTERNALS = buildApiUrl('tx_internal_txs', { hash: TX_HASH });
const hooksConfig = {
router: {
query: { id: TX_HASH },
query: { hash: TX_HASH },
},
};
......
......@@ -76,7 +76,7 @@ const TxInternals = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'tx_internal_txs',
pathParams: { id: txInfo.data?.hash },
pathParams: { hash: txInfo.data?.hash },
options: {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
},
......
......@@ -16,7 +16,7 @@ const TxLogs = () => {
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'tx_logs',
pathParams: { id: txInfo.data?.hash },
pathParams: { hash: txInfo.data?.hash },
options: {
enabled: Boolean(txInfo.data?.hash) && Boolean(txInfo.data?.status),
},
......
......@@ -4,6 +4,7 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { SECOND } from 'lib/consts';
import getQueryParamString from 'lib/router/getQueryParamString';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import RawDataSnippet from 'ui/shared/RawDataSnippet';
import TxPendingAlert from 'ui/tx/TxPendingAlert';
......@@ -12,12 +13,13 @@ import useFetchTxInfo from 'ui/tx/useFetchTxInfo';
const TxRawTrace = () => {
const router = useRouter();
const hash = getQueryParamString(router.query.hash);
const txInfo = useFetchTxInfo({ updateDelay: 5 * SECOND });
const { data, isLoading, isError } = useApiQuery('tx_raw_trace', {
pathParams: { id: router.query.id?.toString() },
pathParams: { hash },
queryOptions: {
enabled: Boolean(router.query.id) && Boolean(txInfo.data?.status),
enabled: Boolean(hash) && Boolean(txInfo.data?.status),
},
});
......
......@@ -36,7 +36,7 @@ const TxTokenTransfer = () => {
const tokenTransferQuery = useQueryWithPages({
resourceName: 'tx_token_transfers',
pathParams: { id: txsInfo.data?.hash.toString() },
pathParams: { hash: txsInfo.data?.hash.toString() },
options: { enabled: Boolean(txsInfo.data?.status && txsInfo.data?.hash) },
filters: { type: typeFilter },
});
......
import { Icon, GridItem, Show, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer';
import tokenIcon from 'icons/token.svg';
import link from 'lib/link/link';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
import { flattenTotal } from 'ui/shared/TokenTransfer/helpers';
......@@ -25,7 +25,7 @@ const TOKEN_TRANSFERS_TYPES = [
const VISIBLE_ITEMS_NUM = 3;
const TxDetailsTokenTransfers = ({ data, txHash }: Props) => {
const viewAllUrl = link('tx', { id: txHash }, { tab: 'token_transfers' });
const viewAllUrl = route({ pathname: '/tx/[hash]', query: { hash: txHash, tab: 'token_transfers' } });
const formattedData = data.reduce(flattenTotal, []);
const transferGroups = TOKEN_TRANSFERS_TYPES.map((group) => ({
......
......@@ -9,6 +9,7 @@ import type { Transaction } from 'types/api/transaction';
import type { ResourceError } from 'lib/api/resources';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import delay from 'lib/delay';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -25,11 +26,12 @@ export default function useFetchTxInfo({ onTxStatusUpdate, updateDelay }: Params
const router = useRouter();
const queryClient = useQueryClient();
const [ socketStatus, setSocketStatus ] = React.useState<'close' | 'error'>();
const hash = getQueryParamString(router.query.hash);
const queryResult = useApiQuery<'tx', { status: number }>('tx', {
pathParams: { id: router.query.id?.toString() },
pathParams: { hash },
queryOptions: {
enabled: Boolean(router.query.id),
enabled: Boolean(hash),
refetchOnMount: false,
},
});
......@@ -38,10 +40,10 @@ export default function useFetchTxInfo({ onTxStatusUpdate, updateDelay }: Params
const handleStatusUpdateMessage: SocketMessage.TxStatusUpdate['handler'] = React.useCallback(async() => {
updateDelay && await delay(updateDelay);
queryClient.invalidateQueries({
queryKey: getResourceKey('tx', { pathParams: { id: router.query.id?.toString() } }),
queryKey: getResourceKey('tx', { pathParams: { hash } }),
});
onTxStatusUpdate?.();
}, [ onTxStatusUpdate, queryClient, router.query.id, updateDelay ]);
}, [ onTxStatusUpdate, queryClient, hash, updateDelay ]);
const handleSocketClose = React.useCallback(() => {
setSocketStatus('close');
......@@ -52,7 +54,7 @@ export default function useFetchTxInfo({ onTxStatusUpdate, updateDelay }: Params
}, []);
const channel = useSocketChannel({
topic: `transactions:${ router.query.id }`,
topic: `transactions:${ hash }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: isLoading || isError || data.status !== null,
......
......@@ -12,7 +12,7 @@ interface Props {
const TxAdditionalInfoContainer = ({ hash }: Props) => {
const { data, isError, isLoading } = useApiQuery('tx', {
pathParams: { id: hash },
pathParams: { hash },
queryOptions: {
refetchOnMount: false,
},
......
import { Box, Heading, Text, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import appConfig from 'configs/app/config';
import getValueWithUnit from 'lib/getValueWithUnit';
import link from 'lib/link/link';
import CurrencyValue from 'ui/shared/CurrencyValue';
import LinkInternal from 'ui/shared/LinkInternal';
import TextSeparator from 'ui/shared/TextSeparator';
......@@ -90,7 +90,7 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
<Text fontWeight="600" as="span">{ tx.position }</Text>
</Box>
</Box>
<LinkInternal fontSize="sm" href={ link('tx', { id: tx.hash }) }>More details</LinkInternal>
<LinkInternal fontSize="sm" href={ route({ pathname: '/tx/[hash]', query: { hash: tx.hash } }) }>More details</LinkInternal>
</>
);
};
......
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