Commit 428ec75e authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #611 from blockscout/no-link

no link: final
parents 5d705e4e 3ebc84a2
......@@ -9,6 +9,7 @@ jobs:
jest_tests:
name: Run tests with Jest
runs-on: ubuntu-latest
if: ${{ false }} # disable since there are no jest test yet
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
......
const PATHS = require('../../lib/link/paths.json');
const oldUrls = [
{
oldPath: '/account/tag_transaction',
newPath: `${ PATHS.private_tags }?tab=tx`,
newPath: '/account/tag_address?tab=tx',
},
{
oldPath: '/pending-transactions',
newPath: `${ PATHS.txs }?tab=pending`,
newPath: '/txs?tab=pending',
},
{
oldPath: '/tx/:id/internal-transactions',
newPath: `${ PATHS.tx }?tab=internal`,
oldPath: '/tx/:hash/internal-transactions',
newPath: '/tx/:hash?tab=internal',
},
{
oldPath: '/tx/:id/logs',
newPath: `${ PATHS.tx }?tab=logs`,
oldPath: '/tx/:hash/logs',
newPath: '/tx/:hash?tab=logs',
},
{
oldPath: '/tx/:id/raw-trace',
newPath: `${ PATHS.tx }?tab=raw_trace`,
oldPath: '/tx/:hash/raw-trace',
newPath: '/tx/:hash?tab=raw_trace',
},
{
oldPath: '/tx/:id/state',
newPath: `${ PATHS.tx }?tab=state`,
oldPath: '/tx/:hash/state',
newPath: '/tx/:hash?tab=state',
},
{
oldPath: '/uncles',
newPath: `${ PATHS.blocks }?tab=uncles`,
newPath: '/blocks?tab=uncles',
},
{
oldPath: '/reorgs',
newPath: `${ PATHS.blocks }?tab=reorgs`,
newPath: '/blocks?tab=reorgs',
},
{
oldPath: '/block/:height/transactions',
newPath: `${ PATHS.block }?tab=txs`,
newPath: '/block/:height?tab=txs',
},
{
oldPath: '/address/:id/transactions',
newPath: `${ PATHS.address_index }`,
oldPath: '/address/:hash/transactions',
newPath: '/address/:hash',
},
{
oldPath: '/address/:id/token-transfers',
newPath: `${ PATHS.address_index }?tab=token_transfers`,
oldPath: '/address/:hash/token-transfers',
newPath: '/address/:hash?tab=token_transfers',
},
{
oldPath: '/address/:id/tokens',
newPath: `${ PATHS.address_index }?tab=tokens`,
oldPath: '/address/:hash/tokens',
newPath: '/address/:hash?tab=tokens',
},
{
oldPath: '/address/:id/internal-transactions',
newPath: `${ PATHS.address_index }?tab=internal_txns`,
oldPath: '/address/:hash/internal-transactions',
newPath: '/address/:hash?tab=internal_txns',
},
{
oldPath: '/address/:id/coin-balances',
newPath: `${ PATHS.address_index }?tab=coin_balance_history`,
oldPath: '/address/:hash/coin-balances',
newPath: '/address/:hash?tab=coin_balance_history',
},
{
oldPath: '/address/:id/validations',
newPath: `${ PATHS.address_index }?tab=blocks_validated`,
oldPath: '/address/:hash/validations',
newPath: '/address/:hash?tab=blocks_validated',
},
{
oldPath: '/address/:id/tokens/:hash/token-transfers',
newPath: `${ PATHS.address_index }?tab=token_transfers&token=:hash`,
oldPath: '/address/:hash/tokens/:token_hash/token-transfers',
newPath: '/address/:hash?tab=token_transfers&token=:token_hash',
},
// contract verification
{
oldPath: '/address/:id/contract_verifications/new',
newPath: `${ PATHS.address_contract_verification }`,
oldPath: '/address/:hash/contract_verifications/new',
newPath: '/address/:hash/contract_verification',
},
{
oldPath: '/address/:id/verify-via-flattened-code/new',
newPath: `${ PATHS.address_contract_verification }?method=flatten_source_code`,
oldPath: '/address/:hash/verify-via-flattened-code/new',
newPath: '/address/:hash/contract_verification?method=flatten_source_code',
},
{
oldPath: '/address/:id/verify-via-standard-json-input/new',
newPath: `${ PATHS.address_contract_verification }?method=standard_input`,
oldPath: '/address/:hash/verify-via-standard-json-input/new',
newPath: '/address/:hash/contract_verification?method=standard_input',
},
{
oldPath: '/address/:id/verify-via-metadata-json/new',
newPath: `${ PATHS.address_contract_verification }?method=sourcify`,
oldPath: '/address/:hash/verify-via-metadata-json/new',
newPath: '/address/:hash/contract_verification?method=sourcify',
},
{
oldPath: '/address/:id/verify-via-multi-part-files/new',
newPath: `${ PATHS.address_contract_verification }?method=multi_part_file`,
oldPath: '/address/:hash/verify-via-multi-part-files/new',
newPath: '/address/:hash/contract_verification?method=multi_part_file',
},
{
oldPath: '/address/:id/verify-vyper-contract/new',
newPath: `${ PATHS.address_contract_verification }?method=vyper_contract`,
oldPath: '/address/:hash/verify-vyper-contract/new',
newPath: '/address/:hash/contract_verification?method=vyper_contract',
},
];
......
......@@ -4,14 +4,14 @@ import appConfig from 'configs/app/config';
import isNeedProxy from './isNeedProxy';
import { RESOURCES } from './resources';
import type { ApiResource, ResourceName } from './resources';
import type { ApiResource, ResourceName, ResourcePathParams } from './resources';
export default function buildUrl(
_resource: ApiResource | ResourceName,
pathParams?: Record<string, string | undefined>,
export default function buildUrl<R extends ResourceName>(
resourceName: R,
pathParams?: ResourcePathParams<R>,
queryParams?: Record<string, string | Array<string> | number | undefined>,
) {
const resource: ApiResource = typeof _resource === 'string' ? RESOURCES[_resource] : _resource;
): string {
const resource: ApiResource = RESOURCES[resourceName];
const baseUrl = isNeedProxy() ? appConfig.baseUrl : (resource.endpoint || appConfig.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = isNeedProxy() ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
......
......@@ -37,6 +37,7 @@ export interface ApiResource {
path: string;
endpoint?: string;
basePath?: string;
pathParams?: Array<string>;
}
export const RESOURCES = {
......@@ -49,21 +50,27 @@ export const RESOURCES = {
},
custom_abi: {
path: '/api/account/v1/user/custom_abis/:id?',
pathParams: [ 'id' as const ],
},
watchlist: {
path: '/api/account/v1/user/watchlist/:id?',
pathParams: [ 'id' as const ],
},
public_tags: {
path: '/api/account/v1/user/public_tags/:id?',
pathParams: [ 'id' as const ],
},
private_tags_address: {
path: '/api/account/v1/user/tags/address/:id?',
pathParams: [ 'id' as const ],
},
private_tags_tx: {
path: '/api/account/v1/user/tags/transaction/:id?',
pathParams: [ 'id' as const ],
},
api_keys: {
path: '/api/account/v1/user/api_keys/:id?',
pathParams: [ 'id' as const ],
},
// STATS
......@@ -79,6 +86,7 @@ export const RESOURCES = {
},
stats_line: {
path: '/api/v1/lines/:id',
pathParams: [ 'id' as const ],
endpoint: appConfig.statsApi.endpoint,
basePath: appConfig.statsApi.basePath,
},
......@@ -98,9 +106,11 @@ export const RESOURCES = {
},
block: {
path: '/api/v2/blocks/:height',
pathParams: [ 'height' as const ],
},
block_txs: {
path: '/api/v2/blocks/:height/transactions',
pathParams: [ 'height' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
......@@ -115,25 +125,30 @@ export const RESOURCES = {
filterFields: [ 'filter' as const, 'type' as const, 'method' as const ],
},
tx: {
path: '/api/v2/transactions/:id',
path: '/api/v2/transactions/:hash',
pathParams: [ 'hash' as const ],
},
tx_internal_txs: {
path: '/api/v2/transactions/:id/internal-transactions',
path: '/api/v2/transactions/:hash/internal-transactions',
pathParams: [ 'hash' as const ],
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',
pathParams: [ 'hash' as const ],
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',
pathParams: [ 'hash' as const ],
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',
pathParams: [ 'hash' as const ],
},
// ADDRESSES
......@@ -145,99 +160,121 @@ export const RESOURCES = {
// ADDRESS
address: {
path: '/api/v2/addresses/:id',
path: '/api/v2/addresses/:hash',
pathParams: [ 'hash' as const ],
},
address_counters: {
path: '/api/v2/addresses/:id/counters',
path: '/api/v2/addresses/:hash/counters',
pathParams: [ 'hash' as const ],
},
// this resource doesn't have pagination, so causing huge problems on some addresses page
// address_token_balances: {
// path: '/api/v2/addresses/:id/token-balances',
// path: '/api/v2/addresses/:hash/token-balances',
// },
address_txs: {
path: '/api/v2/addresses/:id/transactions',
path: '/api/v2/addresses/:hash/transactions',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [ 'filter' as const ],
},
address_internal_txs: {
path: '/api/v2/addresses/:id/internal-transactions',
path: '/api/v2/addresses/:hash/internal-transactions',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ 'filter' as const ],
},
address_token_transfers: {
path: '/api/v2/addresses/:id/token-transfers',
path: '/api/v2/addresses/:hash/token-transfers',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'transaction_index' as const ],
filterFields: [ 'filter' as const, 'type' as const, 'token' as const ],
},
address_blocks_validated: {
path: '/api/v2/addresses/:id/blocks-validated',
path: '/api/v2/addresses/:hash/blocks-validated',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'block_number' as const ],
filterFields: [ ],
},
address_coin_balance: {
path: '/api/v2/addresses/:id/coin-balance-history',
path: '/api/v2/addresses/:hash/coin-balance-history',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'block_number' as const ],
filterFields: [ ],
},
address_coin_balance_chart: {
path: '/api/v2/addresses/:id/coin-balance-history-by-day',
path: '/api/v2/addresses/:hash/coin-balance-history-by-day',
pathParams: [ 'hash' as const ],
},
address_logs: {
path: '/api/v2/addresses/:id/logs',
path: '/api/v2/addresses/:hash/logs',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'transaction_index' as const, 'index' as const, 'block_number' as const ],
filterFields: [ ],
},
address_tokens: {
path: '/api/v2/addresses/:id/tokens',
path: '/api/v2/addresses/:hash/tokens',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'token_name' as const, 'token_type' as const, 'value' as const ],
filterFields: [ 'type' as const ],
},
// CONTRACT
contract: {
path: '/api/v2/smart-contracts/:id',
path: '/api/v2/smart-contracts/:hash',
pathParams: [ 'hash' as const ],
},
contract_methods_read: {
path: '/api/v2/smart-contracts/:id/methods-read',
path: '/api/v2/smart-contracts/:hash/methods-read',
pathParams: [ 'hash' as const ],
},
contract_methods_read_proxy: {
path: '/api/v2/smart-contracts/:id/methods-read-proxy',
path: '/api/v2/smart-contracts/:hash/methods-read-proxy',
pathParams: [ 'hash' as const ],
},
contract_method_query: {
path: '/api/v2/smart-contracts/:id/query-read-method',
path: '/api/v2/smart-contracts/:hash/query-read-method',
pathParams: [ 'hash' as const ],
},
contract_methods_write: {
path: '/api/v2/smart-contracts/:id/methods-write',
path: '/api/v2/smart-contracts/:hash/methods-write',
pathParams: [ 'hash' as const ],
},
contract_methods_write_proxy: {
path: '/api/v2/smart-contracts/:id/methods-write-proxy',
path: '/api/v2/smart-contracts/:hash/methods-write-proxy',
pathParams: [ 'hash' as const ],
},
contract_verification_config: {
path: '/api/v2/smart-contracts/verification/config',
},
contract_verification_via: {
path: '/api/v2/smart-contracts/:id/verification/via/:method',
path: '/api/v2/smart-contracts/:hash/verification/via/:method',
pathParams: [ 'hash' as const, 'method' as const ],
},
// TOKEN
token: {
path: '/api/v2/tokens/:hash',
pathParams: [ 'hash' as const ],
},
token_counters: {
path: '/api/v2/tokens/:hash/counters',
pathParams: [ 'hash' as const ],
},
token_holders: {
path: '/api/v2/tokens/:hash/holders',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'value' as const ],
filterFields: [],
},
token_transfers: {
path: '/api/v2/tokens/:hash/transfers',
pathParams: [ 'hash' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
token_inventory: {
path: '/api/v2/tokens/:hash/instances',
pathParams: [ 'hash' as const ],
paginationFields: [ 'unique_token' as const ],
filterFields: [],
},
......@@ -250,9 +287,11 @@ export const RESOURCES = {
// TOKEN INSTANCE
token_instance: {
path: '/api/v2/tokens/:hash/instances/:id',
pathParams: [ 'hash' as const, 'id' as const ],
},
token_instance_transfers_count: {
path: '/api/v2/tokens/:hash/instances/:id/transfers-count',
pathParams: [ 'hash' as const, 'id' as const ],
},
// HOMEPAGE
......@@ -313,6 +352,15 @@ export type ResourcePaginationKey<R extends ResourceName> = typeof RESOURCES[R]
export const resourceKey = (x: keyof typeof RESOURCES) => x;
type ResourcePathParamName<Resource extends ResourceName> =
typeof RESOURCES[Resource] extends { pathParams: Array<string> } ?
ArrayElement<typeof RESOURCES[Resource]['pathParams']> :
string;
export type ResourcePathParams<Resource extends ResourceName> = typeof RESOURCES[Resource] extends { pathParams: Array<string> } ?
Record<ResourcePathParamName<Resource>, string | undefined> :
never;
export interface ResourceError<T = unknown> {
payload?: T;
status: Response['status'];
......
......@@ -6,10 +6,10 @@ import useFetch from 'lib/hooks/useFetch';
import buildUrl from './buildUrl';
import { RESOURCES } from './resources';
import type { ApiResource } from './resources';
import type { ApiResource, ResourceName, ResourcePathParams } from './resources';
export interface Params {
pathParams?: Record<string, string | undefined>;
export interface Params<R extends ResourceName> {
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal'>;
}
......@@ -17,12 +17,12 @@ export interface Params {
export default function useApiFetch() {
const fetch = useFetch();
return React.useCallback(<R extends keyof typeof RESOURCES, SuccessType = unknown, ErrorType = unknown>(
return React.useCallback(<R extends ResourceName, SuccessType = unknown, ErrorType = unknown>(
resourceName: R,
{ pathParams, queryParams, fetchParams }: Params = {},
{ pathParams, queryParams, fetchParams }: Params<R> = {},
) => {
const resource: ApiResource = RESOURCES[resourceName];
const url = buildUrl(resource, pathParams, queryParams);
const url = buildUrl(resourceName, pathParams, queryParams);
return fetch<SuccessType, ErrorType>(url, {
credentials: 'include',
...(resource.endpoint && appConfig.host === 'localhost' ? {
......
......@@ -5,7 +5,7 @@ import type { ResourceError, ResourceName, ResourcePayload } from './resources';
import type { Params as ApiFetchParams } from './useApiFetch';
import useApiFetch from './useApiFetch';
export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams {
export interface Params<R extends ResourceName, E = unknown> extends ApiFetchParams<R> {
queryOptions?: Omit<UseQueryOptions<unknown, ResourceError<E>, ResourcePayload<R>>, 'queryKey' | 'queryFn'>;
}
......
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import link from 'lib/link/link';
import appConfig from 'configs/app/config';
export default function useLoginUrl() {
const router = useRouter();
return link('auth', {}, { path: router.asPath });
return appConfig.authUrl + route({ pathname: '/auth/auth0', query: { path: router.asPath } });
}
......@@ -3,14 +3,13 @@ import { useRouter } from 'next/router';
import React from 'react';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import { ROUTES } from 'lib/link/routes';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
function getSocketParams(router: NextRouter) {
if (
router.pathname === ROUTES.txs.pattern &&
router.pathname === '/txs' &&
(router.query.tab === 'validated' || router.query.tab === undefined) &&
!router.query.block_number &&
!router.query.page
......@@ -18,12 +17,12 @@ function getSocketParams(router: NextRouter) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (router.pathname === ROUTES.network_index.pattern) {
if (router.pathname === '/') {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (
router.pathname === ROUTES.txs.pattern &&
router.pathname === '/txs' &&
router.query.tab === 'pending' &&
!router.query.block_number &&
!router.query.page
......
export const ACCOUNT_ROUTES: Array<RouteName> = [ 'watchlist', 'private_tags', 'public_tags', 'api_keys', 'custom_abi' ];
import type { RouteName } from 'lib/link/routes';
export default function isAccountRoute(route: RouteName) {
return ACCOUNT_ROUTES.includes(route);
}
import link from './link';
it('makes correct link if there are no params in path', () => {
const result = link('api_keys');
expect(result).toBe('https://blockscout.com/account/api_key');
});
it('makes correct link if there are params in path', () => {
const result = link('token_instance_item', { id: '42', hash: '0x67e90a54AeEA85f21949c645082FE95d77BC1E70' });
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' });
expect(result).toBe('https://blockscout.com/tx/0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2?tab=index&foo=bar');
});
import appConfig from 'configs/app/config';
import { ROUTES } from './routes';
import type { RouteName } from './routes';
const PATH_PARAM_REGEXP = /\/:(\w+)/g;
/**
* @deprecated Please consider to use "import { route } from 'nextjs-routes';" instead
*/
export default function link(
routeName: RouteName,
urlParams?: Record<string, Array<string> | string | undefined>,
queryParams?: Record<string, string>,
): string {
const route = ROUTES[routeName];
if (!route) {
return '';
}
const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
let paramValue = urlParams?.[paramName];
if (Array.isArray(paramValue)) {
// FIXME we don't have yet params as array, but typescript says that we could
// dunno know how to manage it, fix me if you find an issue
paramValue = paramValue.join(',');
}
return paramValue ? `/${ paramValue }` : '';
});
const baseUrl = routeName === 'auth' ? appConfig.authUrl : appConfig.baseUrl;
const url = new URL(path, baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
url.searchParams.append(key, value);
});
return url.toString();
}
{
"network_index": "/",
"watchlist": "/account/watchlist",
"private_tags": "/account/tag_address",
"public_tags": "/account/public_tags_request",
"api_keys": "/account/api_key",
"custom_abi": "/account/custom_abi",
"profile": "/auth/profile",
"txs": "/txs",
"tx": "/tx/:id",
"blocks": "/blocks",
"block": "/block/:height",
"tokens": "/tokens",
"token_index": "/token/:hash",
"token_instance_item": "/token/:hash/instance/:id",
"address_index": "/address/:id",
"address_contract_verification": "/address/:id/contract_verification",
"accounts": "/accounts",
"apps": "/apps",
"app_index": "/apps/:id",
"search_results": "/search-results",
"auth": "/auth/auth0",
"stats": "/stats",
"visualize_sol2uml": "/visualize/sol2uml",
"csv_export": "/csv-export"
}
import appConfig from 'configs/app/config';
import PATHS from './paths.json';
export interface Route {
pattern: string;
crossNetworkNavigation?: boolean; // route will not change when switching networks
}
export type RouteName = keyof typeof ROUTES;
export const ROUTES = {
// NETWORK MAIN PAGE
network_index: {
pattern: PATHS.network_index,
crossNetworkNavigation: true,
},
// ACCOUNT
watchlist: {
pattern: PATHS.watchlist,
},
private_tags: {
pattern: PATHS.private_tags,
},
public_tags: {
pattern: PATHS.public_tags,
},
api_keys: {
pattern: PATHS.api_keys,
},
custom_abi: {
pattern: PATHS.custom_abi,
},
profile: {
pattern: PATHS.profile,
},
// TRANSACTIONS
txs: {
pattern: PATHS.txs,
crossNetworkNavigation: true,
},
tx: {
pattern: PATHS.tx,
},
// BLOCKS
// blocks: {
// pattern: PATHS.blocks,
// crossNetworkNavigation: true,
// },
// block: {
// pattern: PATHS.block,
// },
// TOKENS
tokens: {
pattern: PATHS.tokens,
crossNetworkNavigation: true,
},
token_index: {
pattern: PATHS.token_index,
crossNetworkNavigation: true,
},
token_instance_item: {
pattern: PATHS.token_instance_item,
},
// ADDRESSES
address_index: {
pattern: PATHS.address_index,
crossNetworkNavigation: true,
},
address_contract_verification: {
pattern: PATHS.address_contract_verification,
crossNetworkNavigation: true,
},
// ACCOUNTS
accounts: {
pattern: PATHS.accounts,
crossNetworkNavigation: true,
},
// APPS
apps: {
pattern: PATHS.apps,
},
app_index: {
pattern: PATHS.app_index,
},
stats: {
pattern: PATHS.stats,
},
// SEARCH
search_results: {
pattern: PATHS.search_results,
},
// VISUALIZE
visualize_sol2uml: {
pattern: PATHS.visualize_sol2uml,
},
csv_export: {
pattern: PATHS.csv_export,
},
// AUTH
auth: {
pattern: PATHS.auth,
},
};
// !!! for development purpose only !!!
// don't wanna strict ROUTES to type "Record<string, Route>"
// otherwise we lose benefit of using "keyof typeof ROUTES" for possible route names (it will be any string then)
// but we still want typescript to tell us if routes follow its interface
// so we do this simple type-checking here
//
// another option is to create common enum with all possible route names and use it across the project
// but it is a little bit overwhelming as it seems right now
function checkRoutes(route: Record<string, Route>) {
return route;
}
if (appConfig.isDev) {
checkRoutes(ROUTES);
}
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<'/address/[hash]'>) {
const networkTitle = getNetworkTitle();
return {
title: params ? `${ params.id } - ${ networkTitle }` : '',
title: params ? `${ params.hash } - ${ networkTitle }` : '',
description: params ?
`View the account balance, transactions, and other data for ${ params.id } on the ${ networkTitle }` :
`View the account balance, transactions, and other data for ${ params.hash } on the ${ networkTitle }` :
'',
};
}
export type PageParams = {
id: string;
}
......@@ -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;
}
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { route } from 'nextjs-routes';
import appConfig from 'configs/app/config';
import { NAMES } from 'lib/cookies';
import getCspPolicy from 'lib/csp/getCspPolicy';
import link from 'lib/link/link';
const cspPolicy = getCspPolicy();
......@@ -22,7 +22,7 @@ export function middleware(req: NextRequest) {
const apiToken = req.cookies.get(NAMES.API_TOKEN);
if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) {
const authUrl = link('auth', undefined, { path: req.nextUrl.pathname });
const authUrl = appConfig.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } });
return NextResponse.redirect(authUrl);
}
......
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/address/types';
import getSeo from 'lib/next/address/getSeo';
import ContractVerification from 'ui/pages/ContractVerification';
const ContractVerificationPage: NextPage<PageParams> = ({ id }: PageParams) => {
const { title, description } = getSeo({ id });
const ContractVerificationPage: NextPage<RoutedQuery<'/address/[hash]/contract_verification'>> =
({ hash }: RoutedQuery<'/address/[hash]/contract_verification'>) => {
const { title, description } = getSeo({ hash });
return (
<>
......
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/address/types';
import getSeo from 'lib/next/address/getSeo';
import Address from 'ui/pages/Address';
const AddressPage: NextPage<PageParams> = ({ id }: PageParams) => {
const { title, description } = getSeo({ id });
const AddressPage: NextPage<RoutedQuery<'/address/[hash]'>> = ({ hash }: RoutedQuery<'/address/[hash]'>) => {
const { title, description } = getSeo({ hash });
return (
<>
......
import type { NextPage } from 'next';
const Auth0Page: NextPage = () => {
return null;
};
export default Auth0Page;
export async function getServerSideProps() {
return {
notFound: true,
};
}
import type { NextPage } from 'next';
const CsvExportPage: NextPage = () => {
return null;
};
export default CsvExportPage;
export async function getServerSideProps() {
return {
notFound: true,
};
}
......@@ -7,7 +7,6 @@ import type { SearchRedirectResult } from 'types/api/search';
import buildUrlNode from 'lib/api/buildUrlNode';
import fetchFactory from 'lib/api/nodeFetch';
import link from 'lib/link/link';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import type { Props } from 'lib/next/getServerSideProps';
import { getServerSideProps as getServerSidePropsBase } from 'lib/next/getServerSideProps';
......@@ -47,10 +46,10 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, r
return route({ pathname: '/block/[height]', query: { height: q } });
}
case 'address': {
return link('address_index', { id: payload.parameter || q });
return route({ pathname: '/address/[hash]', query: { hash: 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 (
<>
......
import { compile } from 'path-to-regexp';
import type { ResourceName } from 'lib/api/resources';
import type { ResourceName, ResourcePathParams } from 'lib/api/resources';
import { RESOURCES } from 'lib/api/resources';
export default function buildApiUrl(resourceName: ResourceName, pathParams?: Record<string, string>) {
export default function buildApiUrl<R extends ResourceName>(resourceName: R, pathParams?: ResourcePathParams<R>) {
const resource = RESOURCES[resourceName];
return compile('/node-api/proxy/poa/core' + resource.path)(pathParams);
}
......@@ -12,23 +12,26 @@ declare module "nextjs-routes" {
| StaticRoute<"/account/tag_address">
| StaticRoute<"/account/watchlist">
| StaticRoute<"/accounts">
| DynamicRoute<"/address/[id]/contract_verification", { "id": string }>
| DynamicRoute<"/address/[id]", { "id": string }>
| DynamicRoute<"/address/[hash]/contract_verification", { "hash": string }>
| DynamicRoute<"/address/[hash]", { "hash": string }>
| StaticRoute<"/api/csrf">
| StaticRoute<"/api/proxy">
| DynamicRoute<"/apps/[id]", { "id": string }>
| StaticRoute<"/apps">
| StaticRoute<"/auth/auth0">
| StaticRoute<"/auth/profile">
| DynamicRoute<"/block/[height]", { "height": string }>
| StaticRoute<"/blocks">
| StaticRoute<"/csv-export">
| StaticRoute<"/graph">
| StaticRoute<"/">
| StaticRoute<"/login">
| StaticRoute<"/search-results">
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| DynamicRoute<"/token/[hash]/instance/[id]", { "hash": string; "id": string }>
| StaticRoute<"/tokens">
| DynamicRoute<"/tx/[id]", { "id": string }>
| DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs">
| StaticRoute<"/visualize/sol2uml">;
......
......@@ -31,10 +31,10 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
const queryClient = useQueryClient();
const router = useRouter();
const addressHash = String(router.query?.id);
const addressHash = String(router.query.hash);
const query = useQueryWithPages({
resourceName: 'address_blocks_validated',
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
scrollRef,
});
......@@ -46,7 +46,7 @@ const AddressBlocksValidated = ({ scrollRef }: Props) => {
setSocketAlert(false);
queryClient.setQueryData(
getResourceKey('address_blocks_validated', { pathParams: { id: addressHash } }),
getResourceKey('address_blocks_validated', { pathParams: { hash: addressHash } }),
(prevData: AddressBlocksValidatedResponse | undefined) => {
if (!prevData) {
return;
......
......@@ -8,11 +8,11 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressCoinBalance from './AddressCoinBalance';
const addressHash = 'hash';
const BALANCE_HISTORY_API_URL = buildApiUrl('address_coin_balance', { id: addressHash });
const BALANCE_HISTORY_CHART_API_URL = buildApiUrl('address_coin_balance_chart', { id: addressHash });
const BALANCE_HISTORY_API_URL = buildApiUrl('address_coin_balance', { hash: addressHash });
const BALANCE_HISTORY_CHART_API_URL = buildApiUrl('address_coin_balance_chart', { hash: addressHash });
const hooksConfig = {
router: {
query: { id: addressHash },
query: { hash: addressHash },
},
};
......
......@@ -7,6 +7,7 @@ import type { AddressCoinBalanceHistoryResponse } from 'types/api/address';
import { getResourceKey } from 'lib/api/useApiQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import SocketAlert from 'ui/shared/SocketAlert';
......@@ -20,10 +21,10 @@ const AddressCoinBalance = () => {
const router = useRouter();
const scrollRef = React.useRef<HTMLDivElement>(null);
const addressHash = String(router.query?.id);
const addressHash = getQueryParamString(router.query.hash);
const coinBalanceQuery = useQueryWithPages({
resourceName: 'address_coin_balance',
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
scrollRef,
});
......@@ -35,7 +36,7 @@ const AddressCoinBalance = () => {
setSocketAlert(false);
queryClient.setQueryData(
getResourceKey('address_coin_balance', { pathParams: { id: addressHash } }),
getResourceKey('address_coin_balance', { pathParams: { hash: addressHash } }),
(prevData: AddressCoinBalanceHistoryResponse | undefined) => {
if (!prevData) {
return;
......
import { chakra, Icon, Link, Tooltip, Hide } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import svgFileIcon from 'icons/files/csv.svg';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
interface Props {
address: string;
......@@ -21,7 +21,8 @@ const AddressCsvExportLink = ({ className, address, type }: Props) => {
display="inline-flex"
alignItems="center"
whiteSpace="nowrap"
href={ link('csv_export', undefined, { type, address }) }
href={ route({ pathname: '/csv-export', query: { type, address } }) }
flexShrink={ 0 }
>
<Icon as={ svgFileIcon } boxSize={{ base: '30px', lg: 6 }}/>
<Hide ssr={ false } below="lg"><chakra.span ml={ 1 }>Download CSV</chakra.span></Hide>
......
......@@ -16,14 +16,14 @@ import AddressDetails from './AddressDetails';
import MockAddressPage from './testUtils/MockAddressPage';
const ADDRESS_HASH = addressMock.hash;
const API_URL_ADDRESS = buildApiUrl('address', { id: ADDRESS_HASH });
const API_URL_COUNTERS = buildApiUrl('address_counters', { id: ADDRESS_HASH });
const API_URL_TOKENS_ERC20 = buildApiUrl('address_tokens', { id: ADDRESS_HASH }) + '?type=ERC-20';
const API_URL_TOKENS_ERC721 = buildApiUrl('address_tokens', { id: ADDRESS_HASH }) + '?type=ERC-721';
const API_URL_TOKENS_ER1155 = buildApiUrl('address_tokens', { id: ADDRESS_HASH }) + '?type=ERC-1155';
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_COUNTERS = buildApiUrl('address_counters', { hash: ADDRESS_HASH });
const API_URL_TOKENS_ERC20 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-20';
const API_URL_TOKENS_ERC721 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-721';
const API_URL_TOKENS_ER1155 = buildApiUrl('address_tokens', { hash: ADDRESS_HASH }) + '?type=ERC-1155';
const hooksConfig = {
router: {
query: { id: ADDRESS_HASH },
query: { hash: ADDRESS_HASH },
},
};
......
......@@ -10,7 +10,7 @@ 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 link from 'lib/link/link';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressCounterItem from 'ui/address/details/AddressCounterItem';
import AddressLink from 'ui/shared/address/AddressLink';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
......@@ -33,12 +33,12 @@ interface Props {
const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
const router = useRouter();
const addressHash = router.query.id?.toString();
const addressHash = getQueryParamString(router.query.hash);
const countersQuery = useApiQuery('address_counters', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
queryOptions: {
enabled: Boolean(router.query.id) && Boolean(addressQuery.data),
enabled: Boolean(addressHash) && Boolean(addressQuery.data),
},
});
......@@ -91,7 +91,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
<Flex mt={ 8 } columnGap={ 4 } flexWrap="wrap">
<Text fontSize="sm">Verify with other explorers</Text>
{ explorers.map((explorer) => {
const url = new URL(explorer.paths.address + '/' + router.query.id, explorer.baseUrl);
const url = new URL(explorer.paths.address + '/' + addressHash, explorer.baseUrl);
return <LinkExternal key={ explorer.baseUrl } title={ explorer.title } href={ url.toString() }/>;
}) }
</Flex>
......@@ -119,7 +119,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
hint="Implementation address of the proxy contract."
columnGap={ 1 }
>
<LinkInternal href={ link('address_index', { id: data.implementation_address }) } overflow="hidden">
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: data.implementation_address } }) } overflow="hidden">
{ data.implementation_name || <HashStringShortenDynamic hash={ data.implementation_address }/> }
</LinkInternal>
{ data.implementation_name && (
......
......@@ -9,10 +9,10 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressInternalTxs from './AddressInternalTxs';
const ADDRESS_HASH = internalTxsMock.base.from.hash;
const API_URL_TX_INTERNALS = buildApiUrl('address_internal_txs', { id: ADDRESS_HASH });
const API_URL_TX_INTERNALS = buildApiUrl('address_internal_txs', { hash: ADDRESS_HASH });
const hooksConfig = {
router: {
query: { id: ADDRESS_HASH },
query: { hash: ADDRESS_HASH },
},
};
......
import { Text, Show, Hide } from '@chakra-ui/react';
import castArray from 'lodash/castArray';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -9,6 +8,7 @@ import { AddressFromToFilterValues } from 'types/api/address';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressIntTxsSkeletonDesktop from 'ui/address/internals/AddressIntTxsSkeletonDesktop';
import AddressIntTxsSkeletonMobile from 'ui/address/internals/AddressIntTxsSkeletonMobile';
import AddressIntTxsTable from 'ui/address/internals/AddressIntTxsTable';
......@@ -27,13 +27,11 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
const router = useRouter();
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(getFilterValue(router.query.filter));
const queryId = router.query.id;
const queryIdArray = castArray(queryId);
const queryIdStr = queryIdArray[0];
const hash = getQueryParamString(router.query.hash);
const { data, isLoading, isError, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_internal_txs',
pathParams: { id: queryIdStr },
pathParams: { hash },
filters: { filter: filterValue },
scrollRef,
});
......@@ -70,10 +68,10 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
content = (
<>
<Show below="lg" ssr={ false }>
<AddressIntTxsList data={ data.items } currentAddress={ queryIdStr }/>
<AddressIntTxsList data={ data.items } currentAddress={ hash }/>
</Show>
<Hide below="lg" ssr={ false }>
<AddressIntTxsTable data={ data.items } currentAddress={ queryIdStr }/>
<AddressIntTxsTable data={ data.items } currentAddress={ hash }/>
</Hide>
</>
);
......@@ -87,7 +85,7 @@ const AddressInternalTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
onFilterChange={ handleFilterChange }
isActive={ Boolean(filterValue) }
/>
<AddressCsvExportLink address={ queryIdStr } type="internal-transactions" ml={{ base: 2, lg: 'auto' }}/>
<AddressCsvExportLink address={ hash } type="internal-transactions" ml={{ base: 2, lg: 'auto' }}/>
{ isPaginationVisible && <Pagination ml={{ base: 'auto', lg: 8 }} { ...pagination }/> }
</ActionBar>
{ content }
......
......@@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import LogItem from 'ui/shared/logs/LogItem';
......@@ -12,10 +13,10 @@ import Pagination from 'ui/shared/Pagination';
const AddressLogs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter();
const addressHash = String(router.query?.id);
const hash = getQueryParamString(router.query.hash);
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_logs',
pathParams: { id: addressHash },
pathParams: { hash },
scrollRef,
});
......
......@@ -8,12 +8,12 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressTokenTransfers from './AddressTokenTransfers';
const API_URL = buildApiUrl('address_token_transfers', { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' }) +
const API_URL = buildApiUrl('address_token_transfers', { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' }) +
'?token=0x1189a607CEac2f0E14867de4EB15b15C9FFB5859';
const hooksConfig = {
router: {
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', token: '0x1189a607CEac2f0E14867de4EB15b15C9FFB5859' },
query: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', token_hash: '0x1189a607CEac2f0E14867de4EB15b15C9FFB5859' },
},
};
......
......@@ -16,6 +16,7 @@ import getFilterValuesFromQuery from 'lib/getFilterValuesFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import TOKEN_TYPE from 'lib/token/tokenTypes';
......@@ -70,12 +71,12 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const currentAddress = router.query.id?.toString();
const currentAddress = getQueryParamString(router.query.hash);
const [ socketAlert, setSocketAlert ] = React.useState('');
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const tokenFilter = router.query.token ? router.query.token.toString() : undefined;
const tokenFilter = getQueryParamString(router.query.token_hash) || undefined;
const [ filters, setFilters ] = React.useState<Filters>(
{
......@@ -86,7 +87,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
const { isError, isLoading, data, pagination, onFilterChange, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_token_transfers',
pathParams: { id: currentAddress },
pathParams: { hash: currentAddress },
filters: tokenFilter ? { token: tokenFilter } : filters,
scrollRef,
});
......@@ -118,7 +119,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
}
} else {
queryClient.setQueryData(
getResourceKey('address_token_transfers', { pathParams: { id: router.query.id?.toString() }, queryParams: { ...filters } }),
getResourceKey('address_token_transfers', { pathParams: { hash: currentAddress }, queryParams: { ...filters } }),
(prevData: AddressTokenTransferResponse | undefined) => {
if (!prevData) {
return;
......@@ -148,7 +149,7 @@ const AddressTokenTransfers = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLD
}, []);
const channel = useSocketChannel({
topic: `addresses:${ (router.query.id as string).toLowerCase() }`,
topic: `addresses:${ currentAddress.toLowerCase() }`,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: pagination.page !== 1 || Boolean(tokenFilter),
......
......@@ -10,8 +10,8 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressTokens from './AddressTokens';
const ADDRESS_HASH = withName.hash;
const API_URL_ADDRESS = buildApiUrl('address', { id: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { id: ADDRESS_HASH });
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH });
const nextPageParams = {
items_count: 50,
......@@ -59,7 +59,7 @@ const test = base.extend({
test('erc20 +@mobile +@dark-mode', async({ mount }) => {
const hooksConfig = {
router: {
query: { id: ADDRESS_HASH, tab: 'tokens_erc20' },
query: { hash: ADDRESS_HASH, tab: 'tokens_erc20' },
isReady: true,
},
};
......@@ -78,7 +78,7 @@ test('erc20 +@mobile +@dark-mode', async({ mount }) => {
test('erc721 +@mobile +@dark-mode', async({ mount }) => {
const hooksConfig = {
router: {
query: { id: ADDRESS_HASH, tab: 'tokens_erc721' },
query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' },
isReady: true,
},
};
......@@ -97,7 +97,7 @@ test('erc721 +@mobile +@dark-mode', async({ mount }) => {
test('erc1155 +@mobile +@dark-mode', async({ mount }) => {
const hooksConfig = {
router: {
query: { id: ADDRESS_HASH, tab: 'tokens_erc1155' },
query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' },
isReady: true,
},
};
......
......@@ -36,7 +36,7 @@ const AddressTokens = () => {
const tokensQuery = useQueryWithPages({
resourceName: 'address_tokens',
pathParams: { id: router.query.id?.toString() },
pathParams: { hash: router.query.hash?.toString() },
filters: { type: tokenType },
scrollRef,
});
......
......@@ -8,11 +8,11 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import AddressTxs from './AddressTxs';
const API_URL = buildApiUrl('address_txs', { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' });
const API_URL = buildApiUrl('address_txs', { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' });
const hooksConfig = {
router: {
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' },
query: { hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' },
},
};
......
......@@ -10,6 +10,7 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import getFilterValueFromQuery from 'lib/getFilterValueFromQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import ActionBar from 'ui/shared/ActionBar';
......@@ -31,13 +32,13 @@ const AddressTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}
const [ newItemsCount, setNewItemsCount ] = React.useState(0);
const isMobile = useIsMobile();
const currentAddress = router.query.id?.toString();
const currentAddress = getQueryParamString(router.query.hash);
const [ filterValue, setFilterValue ] = React.useState<AddressFromToFilter>(getFilterValue(router.query.filter));
const addressTxsQuery = useQueryWithPages({
resourceName: 'address_txs',
pathParams: { id: currentAddress },
pathParams: { hash: currentAddress },
filters: { filter: filterValue },
scrollRef,
});
......@@ -63,7 +64,7 @@ const AddressTxs = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}
}
queryClient.setQueryData(
getResourceKey('address_txs', { pathParams: { id: router.query.id?.toString() }, queryParams: { filter: filterValue } }),
getResourceKey('address_txs', { pathParams: { hash: currentAddress }, queryParams: { filter: filterValue } }),
(prevData: AddressTransactionsResponse | undefined) => {
if (!prevData) {
return;
......
......@@ -11,7 +11,7 @@ interface Props {
const AddressCoinBalanceChart = ({ addressHash }: Props) => {
const { data, isLoading, isError } = useApiQuery('address_coin_balance_chart', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
});
const items = React.useMemo(() => data?.map(({ date, value }) => ({
......
......@@ -8,10 +8,10 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import ContractCode from './ContractCode';
const addressHash = 'hash';
const CONTRACT_API_URL = buildApiUrl('contract', { id: addressHash });
const CONTRACT_API_URL = buildApiUrl('contract', { hash: addressHash });
const hooksConfig = {
router: {
query: { id: addressHash },
query: { hash: addressHash },
},
};
......
import { Flex, Skeleton, Button, Grid, GridItem, Text, Alert, Link, chakra, Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import link from 'lib/link/link';
import getQueryParamString from 'lib/router/getQueryParamString';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -24,9 +25,9 @@ const InfoItem = ({ label, value }: { label: string; value: string }) => (
const ContractCode = () => {
const router = useRouter();
const addressHash = router.query.id?.toString();
const addressHash = getQueryParamString(router.query.hash);
const { data, isLoading, isError } = useApiQuery('contract', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
queryOptions: {
enabled: Boolean(addressHash),
refetchOnMount: false,
......@@ -60,7 +61,7 @@ const ContractCode = () => {
ml="auto"
mr={ 3 }
as="a"
href={ link('address_contract_verification', { id: addressHash }) }
href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: addressHash } }) }
>
Verify & publish
</Button>
......@@ -74,7 +75,7 @@ const ContractCode = () => {
const decoded = data.decoded_constructor_args
.map(([ value, { name, type } ], index) => {
const valueEl = type === 'address' ?
<LinkInternal href={ link('address_index', { id: value }) }>{ value }</LinkInternal> :
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: value } }) }>{ value }</LinkInternal> :
<span>{ value }</span>;
return (
<Box key={ index }>
......@@ -101,7 +102,7 @@ const ContractCode = () => {
return data.external_libraries.map((item) => (
<Box key={ item.address_hash }>
<chakra.span fontWeight={ 500 }>{ item.name }: </chakra.span>
<LinkInternal href={ link('address_index', { id: item.address_hash }, { tab: 'contract' }) }>{ item.address_hash }</LinkInternal>
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: item.address_hash, tab: 'contract' } }) }>{ item.address_hash }</LinkInternal>
</Box>
));
})();
......@@ -129,7 +130,7 @@ const ContractCode = () => {
<AddressLink type="address" hash={ data.verified_twin_address_hash } truncation="constant" ml={ 2 }/>
</Address>
<chakra.span mt={ 1 }>All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with </chakra.span>
<LinkInternal href={ link('address_contract_verification', { id: addressHash }) }>Verify & Publish</LinkInternal>
<LinkInternal href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: addressHash } }) }>Verify & Publish</LinkInternal>
<span> page</span>
</Alert>
) }
......
......@@ -14,7 +14,7 @@ interface Props {
const ContractImplementationAddress = ({ hash }: Props) => {
const queryClient = useQueryClient();
const data = queryClient.getQueryData<TAddress>(getResourceKey('address', {
pathParams: { id: hash },
pathParams: { hash },
}));
if (!data?.implementation_address) {
......
......@@ -8,11 +8,11 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import ContractRead from './ContractRead';
const addressHash = 'hash';
const CONTRACT_READ_METHODS_API_URL = buildApiUrl('contract_methods_read', { id: addressHash }) + '?is_custom_abi=false';
const CONTRACT_QUERY_METHOD_API_URL = buildApiUrl('contract_method_query', { id: addressHash });
const CONTRACT_READ_METHODS_API_URL = buildApiUrl('contract_methods_read', { hash: addressHash }) + '?is_custom_abi=false';
const CONTRACT_QUERY_METHOD_API_URL = buildApiUrl('contract_method_query', { hash: addressHash });
const hooksConfig = {
router: {
query: { id: addressHash },
query: { hash: addressHash },
},
};
......
......@@ -7,6 +7,7 @@ import type { SmartContractReadMethod, SmartContractQueryMethodRead } from 'type
import useApiFetch from 'lib/api/useApiFetch';
import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordion';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
......@@ -28,21 +29,21 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => {
const apiFetch = useApiFetch();
const { address: userAddress } = useAccount();
const addressHash = router.query.id?.toString();
const addressHash = getQueryParamString(router.query.hash);
const { data, isLoading, isError } = useApiQuery(isProxy ? 'contract_methods_read_proxy' : 'contract_methods_read', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
queryParams: {
is_custom_abi: isCustomAbi ? 'true' : 'false',
},
queryOptions: {
enabled: Boolean(router.query.id),
enabled: Boolean(addressHash),
},
});
const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array<string | Array<string>>) => {
return apiFetch<'contract_method_query', SmartContractQueryMethodRead>('contract_method_query', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
fetchParams: {
method: 'POST',
body: {
......
import { Box, chakra, Flex, Text, Tooltip } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SmartContract } from 'types/api/contract';
import link from 'lib/link/link';
import CodeEditor from 'ui/shared/CodeEditor';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -28,7 +28,7 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
const diagramLink = hasSol2Yml && address ? (
<Tooltip label="Visualize contract code using Sol2Uml JS library">
<LinkInternal
href={ link('visualize_sol2uml', undefined, { address }) }
href={ route({ pathname: '/visualize/sol2uml', query: { address } }) }
ml="auto"
mr={ 3 }
>
......
......@@ -8,10 +8,10 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import ContractWrite from './ContractWrite';
const addressHash = 'hash';
const CONTRACT_WRITE_METHODS_API_URL = buildApiUrl('contract_methods_write', { id: addressHash }) + '?is_custom_abi=false';
const CONTRACT_WRITE_METHODS_API_URL = buildApiUrl('contract_methods_write', { hash: addressHash }) + '?is_custom_abi=false';
const hooksConfig = {
router: {
query: { id: addressHash },
query: { hash: addressHash },
},
};
......
......@@ -7,6 +7,7 @@ import type { SmartContractWriteMethod } from 'types/api/contract';
import config from 'configs/app/config';
import useApiQuery from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordion';
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
......@@ -27,12 +28,12 @@ interface Props {
const ContractWrite = ({ isProxy, isCustomAbi }: Props) => {
const router = useRouter();
const addressHash = router.query.id?.toString();
const addressHash = getQueryParamString(router.query.hash);
const { data: signer } = useSigner();
const { isConnected } = useAccount();
const { data, isLoading, isError } = useApiQuery(isProxy ? 'contract_methods_write_proxy' : 'contract_methods_write', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
queryParams: {
is_custom_abi: isCustomAbi ? 'true' : 'false',
},
......
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) {
......
......@@ -28,9 +28,9 @@ export function ContractContextProvider({ children }: ProviderProps) {
const { data: signer } = useSigner();
const queryClient = useQueryClient();
const addressHash = router.query.id?.toString();
const addressHash = router.query.hash?.toString();
const { data: contractInfo } = useApiQuery('contract', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
queryOptions: {
enabled: Boolean(addressHash),
refetchOnMount: false,
......@@ -38,11 +38,11 @@ export function ContractContextProvider({ children }: ProviderProps) {
});
const addressInfo = queryClient.getQueryData<Address>(getResourceKey('address', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
}));
const { data: proxyInfo } = useApiQuery('contract', {
pathParams: { id: addressInfo?.implementation_address || '' },
pathParams: { hash: addressInfo?.implementation_address || '' },
queryOptions: {
enabled: Boolean(addressInfo?.implementation_address),
refetchOnMount: false,
......
......@@ -26,7 +26,7 @@ const AddressBalance = ({ data }: Props) => {
}
setLastBlockNumber(blockNumber);
const queryKey = getResourceKey('address', { pathParams: { id: data.hash } });
const queryKey = getResourceKey('address', { pathParams: { hash: data.hash } });
queryClient.setQueryData(queryKey, (prevData: Address | undefined) => {
if (!prevData) {
return;
......
import { Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressCounters } from 'types/api/address';
import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props {
......@@ -42,7 +42,7 @@ const AddressCounterItem = ({ prop, query, address, onClick }: Props) => {
return <span>0</span>;
}
return (
<LinkInternal href={ link('address_index', { id: address }, { tab: PROP_TO_TAB[prop] }) } onClick={ onClick }>
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: address, tab: PROP_TO_TAB[prop] } }) } onClick={ onClick }>
{ Number(data).toLocaleString() }
</LinkInternal>
);
......
......@@ -41,10 +41,10 @@ const AddressFavoriteButton = ({ className, hash, isAdded }: Props) => {
}, [ addModalProps, deleteModalProps, isAdded, isAuth, loginUrl ]);
const handleAddOrDeleteSuccess = React.useCallback(async() => {
const queryKey = getResourceKey('address', { pathParams: { id: router.query.id?.toString() } });
const queryKey = getResourceKey('address', { pathParams: { hash: router.query.hash?.toString() } });
await queryClient.refetchQueries({ queryKey });
addModalProps.onClose();
}, [ addModalProps, queryClient, router.query.id ]);
}, [ addModalProps, queryClient, router.query.hash ]);
const handleAddModalClose = React.useCallback(() => {
addModalProps.onClose();
......
import { route } from 'nextjs-routes';
import React from 'react';
import type { Address } from 'types/api/address';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -19,7 +19,7 @@ const AddressNameInfo = ({ data }: Props) => {
title="Token name"
hint="Token name and symbol"
>
<LinkInternal href={ link('token_index', { hash: data.token.address }) }>
<LinkInternal href={ route({ pathname: '/token/[hash]', query: { hash: data.token.address } }) }>
{ data.token.name }{ symbol }
</LinkInternal>
</DetailsInfoItem>
......
......@@ -7,8 +7,8 @@ const MockAddressPage = ({ children }: { children: JSX.Element }): JSX.Element =
const router = useRouter();
const { data } = useApiQuery('address', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
pathParams: { hash: router.query.hash?.toString() },
queryOptions: { enabled: Boolean(router.query.hash) },
});
if (!data) {
......
......@@ -12,13 +12,13 @@ import MockAddressPage from 'ui/address/testUtils/MockAddressPage';
import TokenSelect from './TokenSelect';
const ASSET_URL = 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/poa/assets/0xb2a90505dc6680a7a695f7975d0d32EeF610f456/logo.png';
const TOKENS_ERC20_API_URL = buildApiUrl('address_tokens', { id: '1' }) + '?type=ERC-20';
const TOKENS_ERC721_API_URL = buildApiUrl('address_tokens', { id: '1' }) + '?type=ERC-721';
const TOKENS_ER1155_API_URL = buildApiUrl('address_tokens', { id: '1' }) + '?type=ERC-1155';
const ADDRESS_API_URL = buildApiUrl('address', { id: '1' });
const TOKENS_ERC20_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-20';
const TOKENS_ERC721_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-721';
const TOKENS_ER1155_API_URL = buildApiUrl('address_tokens', { hash: '1' }) + '?type=ERC-1155';
const ADDRESS_API_URL = buildApiUrl('address', { hash: '1' });
const hooksConfig = {
router: {
query: { id: '1' },
query: { hash: '1' },
},
};
const CLIPPING_AREA = { x: 0, y: 0, width: 360, height: 500 };
......
......@@ -11,6 +11,7 @@ import type { Address } from 'types/api/address';
import walletIcon from 'icons/wallet.svg';
import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -28,13 +29,13 @@ const TokenSelect = ({ onClick }: Props) => {
const queryClient = useQueryClient();
const [ blockNumber, setBlockNumber ] = React.useState<number>();
const addressHash = router.query.id?.toString();
const addressResourceKey = getResourceKey('address', { pathParams: { id: addressHash } });
const addressHash = getQueryParamString(router.query.hash);
const addressResourceKey = getResourceKey('address', { pathParams: { hash: addressHash } });
const addressQueryData = queryClient.getQueryData<Address>(addressResourceKey);
const { data, isError, isLoading, refetch } = useFetchTokens({ hash: addressQueryData?.hash });
const tokensResourceKey = getResourceKey('address_tokens', { pathParams: { id: addressQueryData?.hash }, queryParams: { type: 'ERC-20' } });
const tokensResourceKey = getResourceKey('address_tokens', { pathParams: { hash: addressQueryData?.hash }, queryParams: { type: 'ERC-20' } });
const tokensIsFetching = useIsFetching({ queryKey: tokensResourceKey });
const handleTokenBalanceMessage: SocketMessage.AddressTokenBalance['handler'] = React.useCallback((payload) => {
......@@ -82,7 +83,7 @@ const TokenSelect = ({ onClick }: Props) => {
}
<Tooltip label="Show all tokens">
<Box>
<NextLink href={{ pathname: '/address/[id]', query: { id: addressHash || '', tab: 'tokens' } }} passHref>
<NextLink href={{ pathname: '/address/[hash]', query: { hash: addressHash, tab: 'tokens' } }} passHref>
<IconButton
aria-label="Show all tokens"
variant="outline"
......
import { chakra, Flex, Text, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenLogo from 'ui/shared/TokenLogo';
......@@ -45,7 +45,7 @@ const TokenSelectItem = ({ data }: Props) => {
})();
// TODO add filter param when token page is ready
const url = link('token_index', { hash: data.token.address });
const url = route({ pathname: '/token/[hash]', query: { hash: data.token.address } });
return (
<Flex
......
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressTokenBalance } from 'types/api/address';
import link from 'lib/link/link';
import NftImage from 'ui/shared/nft/NftImage';
import TokenLogo from 'ui/shared/TokenLogo';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
......@@ -11,7 +11,7 @@ import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = AddressTokenBalance;
const NFTItem = ({ token, token_id: tokenId }: Props) => {
const tokenLink = link('token_index', { hash: token.address });
const tokenLink = route({ pathname: '/token/[hash]', query: { hash: token.address } });
return (
<LinkBox
......@@ -41,7 +41,7 @@ const NFTItem = ({ token, token_id: tokenId }: Props) => {
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
href={ link('token_instance_item', { hash: token.address, id: tokenId }) }
href={ route({ pathname: '/token/[hash]/instance/[id]', query: { hash: token.address, id: tokenId } }) }
>
{ tokenId }
</Link>
......
......@@ -15,10 +15,10 @@ import TokenBalancesItem from './TokenBalancesItem';
const TokenBalances = () => {
const router = useRouter();
const hash = router.query.id?.toString();
const hash = router.query.hash?.toString();
const addressQuery = useApiQuery('address', {
pathParams: { id: hash },
pathParams: { hash },
queryOptions: { enabled: Boolean(hash) },
});
......
......@@ -10,17 +10,17 @@ interface Props {
export default function useFetchTokens({ hash }: Props) {
const erc20query = useApiQuery('address_tokens', {
pathParams: { id: hash },
pathParams: { hash },
queryParams: { type: 'ERC-20' },
queryOptions: { enabled: Boolean(hash), refetchOnMount: false },
});
const erc721query = useApiQuery('address_tokens', {
pathParams: { id: hash },
pathParams: { hash },
queryParams: { type: 'ERC-721' },
queryOptions: { enabled: Boolean(hash), refetchOnMount: false },
});
const erc1155query = useApiQuery('address_tokens', {
pathParams: { id: hash },
pathParams: { hash },
queryParams: { type: 'ERC-1155' },
queryOptions: { enabled: Boolean(hash), refetchOnMount: false },
});
......
......@@ -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>
......
......@@ -56,7 +56,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
try {
await apiFetch('contract_verification_via', {
pathParams: { method: data.method, id: hash },
pathParams: { method: data.method, hash },
fetchParams: {
method: 'POST',
body,
......@@ -87,7 +87,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
variant: 'subtle',
isClosable: true,
onCloseComplete: () => {
router.push({ pathname: '/address/[id]', query: { id: hash, tab: 'contract' } }, undefined, { shallow: true });
router.push({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } }, undefined, { shallow: true });
},
});
}, [ hash, router, setError, toast ]);
......
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import { ROUTES } from 'lib/link/routes';
import * as statsMock from 'mocks/stats/index';
import * as txMock from 'mocks/txs/tx';
import * as socketServer from 'playwright/fixtures/socketServer';
......@@ -42,7 +41,7 @@ test.describe('socket', () => {
const hooksConfig = {
router: {
pathname: ROUTES.network_index.pattern,
pathname: '/',
query: {},
},
};
......
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 }
......
......@@ -9,6 +9,7 @@ import iconSuccess from 'icons/status/success.svg';
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import notEmpty from 'lib/notEmpty';
import getQueryParamString from 'lib/router/getQueryParamString';
import AddressBlocksValidated from 'ui/address/AddressBlocksValidated';
import AddressCoinBalance from 'ui/address/AddressCoinBalance';
import AddressContract from 'ui/address/AddressContract';
......@@ -44,10 +45,11 @@ const AddressPageContent = () => {
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/accounts');
const tabsScrollRef = React.useRef<HTMLDivElement>(null);
const hash = getQueryParamString(router.query.hash);
const addressQuery = useApiQuery('address', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
pathParams: { hash },
queryOptions: { enabled: Boolean(hash) },
});
const tags = [
......
......@@ -26,11 +26,11 @@ const ContractVerification = () => {
const hasGoBackLink = referrer && referrer.includes('/address');
const router = useRouter();
const hash = getQueryParamString(router.query.id);
const hash = getQueryParamString(router.query.hash);
const method = getQueryParamString(router.query.method) as SmartContractVerificationMethod;
const contractQuery = useApiQuery('contract', {
pathParams: { id: hash },
pathParams: { hash },
queryOptions: {
enabled: Boolean(hash),
},
......@@ -55,7 +55,7 @@ const ContractVerification = () => {
React.useEffect(() => {
if (method && hash) {
router.replace({ pathname: '/address/[id]/contract_verification', query: { id: hash } }, undefined, { scroll: false, shallow: true });
router.replace({ pathname: '/address/[hash]/contract_verification', query: { hash } }, undefined, { scroll: false, shallow: true });
}
// onMount only
// eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -65,7 +65,7 @@ const ContractVerification = () => {
React.useEffect(() => {
if (isVerifiedContract) {
router.push({ pathname: '/address/[id]', query: { id: hash, tab: 'contract' } }, undefined, { scroll: false, shallow: true });
router.push({ pathname: '/address/[hash]', query: { hash, tab: 'contract' } }, undefined, { scroll: false, shallow: true });
}
}, [ hash, isVerifiedContract, router ]);
......
import { Box, Center, useColorMode } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import type { AppItemOverview } from 'types/client/apps';
import appConfig from 'configs/app/config';
import link from 'lib/link/link';
import ContentLoader from 'ui/shared/ContentLoader';
import Page from 'ui/shared/Page/Page';
......@@ -26,9 +26,9 @@ const MarketplaceApp = ({ app, isLoading }: Props) => {
if (app && !isFrameLoading) {
const message = {
blockscoutColorMode: colorMode,
blockscoutRootUrl: link('network_index'),
blockscoutAddressExplorerUrl: link('address_index'),
blockscoutTransactionExplorerUrl: link('tx'),
blockscoutRootUrl: appConfig.baseUrl + route({ pathname: '/' }),
blockscoutAddressExplorerUrl: appConfig.baseUrl + route({ pathname: '/address/[hash]', query: { hash: '' } }),
blockscoutTransactionExplorerUrl: appConfig.baseUrl + route({ pathname: '/tx/[hash]', query: { hash: '' } }),
blockscoutNetworkName: appConfig.network.name,
blockscoutNetworkId: Number(appConfig.network.id),
blockscoutNetworkCurrency: appConfig.network.currency,
......
......@@ -12,7 +12,7 @@ import Token from './Token';
const TOKEN_API_URL = buildApiUrl('token', { hash: '1' });
const TOKEN_COUNTERS_API_URL = buildApiUrl('token_counters', { hash: '1' });
const TOKEN_TRANSFERS_API_URL = buildApiUrl('token_transfers', { hash: '1' });
const ADDRESS_API_URL = buildApiUrl('address', { id: '1' });
const ADDRESS_API_URL = buildApiUrl('address', { hash: '1' });
const hooksConfig = {
router: {
query: { hash: 1, tab: 'token_transfers' },
......
......@@ -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() }/>;
});
......
......@@ -7,7 +7,6 @@ import type { SearchResultItem } from 'types/api/search';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
......@@ -32,7 +31,7 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
return (
<Flex alignItems="flex-start">
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/>
<LinkInternal ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 } wordBreak="break-all">
<LinkInternal ml={ 2 } href={ route({ pathname: '/token/[hash]', query: { hash: data.address } }) } fontWeight={ 700 } wordBreak="break-all">
<chakra.span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</LinkInternal>
</Flex>
......
......@@ -7,7 +7,6 @@ import type { SearchResultItem } from 'types/api/search';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
......@@ -32,7 +31,7 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
<Td fontSize="sm">
<Flex alignItems="center">
<TokenLogo boxSize={ 6 } hash={ data.address } name={ data.name } flexShrink={ 0 }/>
<LinkInternal ml={ 2 } href={ link('token_index', { hash: data.address }) } fontWeight={ 700 } wordBreak="break-all">
<LinkInternal ml={ 2 } href={ route({ pathname: '/token/[hash]', query: { hash: data.address } }) } fontWeight={ 700 } wordBreak="break-all">
<span dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}/>
</LinkInternal>
</Flex>
......@@ -55,7 +54,12 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
<Td fontSize="sm">
<Flex alignItems="center" overflow="hidden">
<AddressIcon address={{ hash: data.address, is_contract: data.type === 'contract', implementation_name: null }} mr={ 2 } flexShrink={ 0 }/>
<LinkInternal href={ link('address_index', { id: data.address }) } fontWeight={ 700 } overflow="hidden" whiteSpace="nowrap">
<LinkInternal
href={ route({ pathname: '/address/[hash]', query: { hash: data.address } }) }
fontWeight={ 700 }
overflow="hidden"
whiteSpace="nowrap"
>
<Box as={ shouldHighlightHash ? 'mark' : 'span' } display="block">
<HashStringShortenDynamic hash={ data.address }/>
</Box>
......
import { Box, Button, Heading, Icon, Text, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import icon404 from 'icons/error-pages/404.svg';
import icon422 from 'icons/error-pages/422.svg';
import icon500 from 'icons/error-pages/500.svg';
import link from 'lib/link/link';
interface Props {
statusCode: number;
......@@ -42,7 +42,7 @@ const AppError = ({ statusCode, className }: Props) => {
size="lg"
variant="outline"
as="a"
href={ link('network_index') }
href={ route({ pathname: '/' }) }
>
Back to home
</Button>
......
......@@ -10,6 +10,7 @@ import {
chakra,
} from '@chakra-ui/react';
import type { StyleProps } from '@chakra-ui/styled-system';
import _pickBy from 'lodash/pickBy';
import { useRouter } from 'next/router';
import React, { useEffect, useRef, useState } from 'react';
......@@ -51,8 +52,9 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
const handleTabChange = React.useCallback((index: number) => {
const nextTab = tabs[index];
const queryForPathname = _pickBy(router.query, (value, key) => router.pathname.includes(`[${ key }]`));
router.push(
{ pathname: router.asPath.split('?')[0], query: { tab: nextTab.id } } as Parameters<typeof router.push>[0],
{ pathname: router.pathname, query: { ...queryForPathname, tab: nextTab.id } },
undefined,
{ shallow: true },
);
......
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: {},
},
};
......
import { Box, Icon, Link, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import nftPlaceholder from 'icons/nft_shield.svg';
import link from 'lib/link/link';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
interface Props {
......@@ -14,7 +14,7 @@ interface Props {
const TokenTransferNft = ({ hash, id, className }: Props) => {
return (
<Link
href={ link('token_instance_item', { hash, id }) }
href={ route({ pathname: '/token/[hash]/instance/[id]', query: { hash, id } }) }
overflow="hidden"
whiteSpace="nowrap"
display="flex"
......
......@@ -4,7 +4,6 @@ import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
import HashStringShorten from 'ui/shared/HashStringShorten';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -45,15 +44,15 @@ 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 });
url = route({ pathname: '/token/[hash]', query: { hash } });
} else if (type === 'block') {
url = route({ pathname: '/block/[height]', query: { height: props.height } });
} else if (type === 'address_token') {
url = link('address_index', { id: hash }, { tab: 'token_transfers', token: props.tokenHash, scroll_to_tabs: 'true' });
url = route({ pathname: '/address/[hash]', query: { hash, tab: 'token_transfers', token_hash: props.tokenHash, scroll_to_tabs: 'true' } });
} else {
url = link('address_index', { id: hash });
url = route({ pathname: '/address/[hash]', query: { hash } });
}
const content = (() => {
......
import { Text, Grid, GridItem, Tooltip, Button, useColorModeValue, Alert, Link } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Log } from 'types/api/log';
// import searchIcon from 'icons/search.svg';
import { space } from 'lib/html-entities';
import link from 'lib/link/link';
import notEmpty from 'lib/notEmpty';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
......@@ -47,7 +47,7 @@ const LogItem = ({ address, index, topics, data, decoded, type, tx_hash: txHash
<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>
<Link href={ route({ pathname: '/address/[hash]/contract_verification', query: { hash: address.hash } }) }>Verify the contract here</Link>
</Alert>
</GridItem>
) }
......
import { Icon, Box, Image, useColorModeValue } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import appConfig from 'configs/app/config';
import smallLogoPlaceholder from 'icons/networks/icons/placeholder.svg';
import logoPlaceholder from 'icons/networks/logos/blockscout.svg';
import link from 'lib/link/link';
import ASSETS from 'lib/networks/networkAssets';
interface Props {
......@@ -14,7 +14,7 @@ interface Props {
const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
const logoColor = useColorModeValue('blue.600', 'white');
const href = link('network_index');
const href = route({ pathname: '/' });
const [ isLogoError, setLogoError ] = React.useState(false);
const [ isSmallLogoError, setSmallLogoError ] = React.useState(false);
......
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, useDisclosure } from '@chakra-ui/react';
import _debounce from 'lodash/debounce';
import { route } from 'nextjs-routes';
import type { FormEvent, FocusEvent } from 'react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import link from 'lib/link/link';
import SearchBarInput from './SearchBarInput';
import SearchBarSuggest from './SearchBarSuggest';
......@@ -26,7 +26,7 @@ const SearchBar = ({ isHomepage }: Props) => {
const handleSubmit = React.useCallback((event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (searchTerm) {
const url = link('search_results', undefined, { q: searchTerm });
const url = route({ pathname: '/search-results', query: { q: searchTerm } });
window.location.assign(url);
}
}, [ searchTerm ]);
......
......@@ -7,7 +7,6 @@ import type { SearchResultItem } from 'types/api/search';
import blockIcon from 'icons/block.svg';
import txIcon from 'icons/transactions.svg';
import highlightText from 'lib/highlightText';
import link from 'lib/link/link';
import AddressIcon from 'ui/shared/address/AddressIcon';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import TokenLogo from 'ui/shared/TokenLogo';
......@@ -23,14 +22,14 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm }: Props) => {
const url = (() => {
switch (data.type) {
case 'token': {
return link('token_index', { hash: data.address });
return route({ pathname: '/token/[hash]', query: { hash: data.address } });
}
case 'contract':
case 'address': {
return link('address_index', { id: data.address });
return route({ pathname: '/address/[hash]', query: { hash: 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) } });
......
......@@ -28,7 +28,7 @@ function composeSources(contract: SmartContract | undefined) {
const Sol2UmlDiagram = ({ addressHash }: Props) => {
const contractQuery = useApiQuery<'contract', ResourceError>('contract', {
pathParams: { id: addressHash },
pathParams: { hash: addressHash },
queryOptions: {
enabled: Boolean(addressHash),
refetchOnMount: false,
......
......@@ -16,7 +16,7 @@ const TokenContractInfo = ({ tokenQuery }: Props) => {
const router = useRouter();
const contractQuery = useApiQuery('address', {
pathParams: { id: router.query.hash?.toString() },
pathParams: { hash: router.query.hash?.toString() },
queryOptions: { enabled: Boolean(router.query.hash) },
});
......
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue, Hide } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TokenInstance } from 'types/api/token';
import link from 'lib/link/link';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -13,7 +13,7 @@ import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = { item: TokenInstance };
const NFTItem = ({ item }: Props) => {
const tokenLink = link('token_instance_item', { hash: item.token.address, id: item.id });
const tokenLink = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: item.token.address, id: item.id } });
return (
<LinkBox
......
......@@ -8,7 +8,7 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import TokenInstanceDetails from './TokenInstanceDetails';
const API_URL_ADDRESS = buildApiUrl('address', { id: tokenInstanceMock.base.token.address });
const API_URL_ADDRESS = buildApiUrl('address', { hash: tokenInstanceMock.base.token.address });
const API_URL_TOKEN_TRANSFERS_COUNT = buildApiUrl('token_instance_transfers_count', {
id: tokenInstanceMock.base.id,
hash: tokenInstanceMock.base.token.address,
......
......@@ -14,7 +14,7 @@ interface Props {
const TokenInstanceCreatorAddress = ({ hash }: Props) => {
const addressQuery = useApiQuery('address', {
pathParams: { id: hash },
pathParams: { hash },
});
if (addressQuery.isError) {
......
import { route } from 'nextjs-routes';
import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import link from 'lib/link/link';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import LinkInternal from 'ui/shared/LinkInternal';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
......@@ -30,7 +30,7 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
}
const url = transfersCountQuery.data.transfers_count > 0 ?
link('token_instance_item', { id, hash }, { tab: 'token_transfers' }) :
route({ pathname: '/token/[hash]/instance/[id]', query: { hash, id, tab: 'token_transfers' } }) :
undefined;
return (
......
import { Flex, Link, Text, Icon, Box } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import nftIcon from 'icons/nft_shield.svg';
import link from 'lib/link/link';
import AddressLink from 'ui/shared/address/AddressLink';
import HashStringShorten from 'ui/shared/HashStringShorten';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
......@@ -17,7 +17,7 @@ interface Props {
const NftTokenTransferSnippet = ({ value, name, hash, symbol, tokenId }: Props) => {
const num = value === '1' ? '' : value;
const url = link('token_instance_item', { hash: hash, id: tokenId });
const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: hash, id: tokenId } });
return (
<Flex alignItems="center" columnGap={ 3 } rowGap={ 2 } flexWrap="wrap">
......
......@@ -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 { Flex, Link, Icon, chakra } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { TxAction, TxActionGeneral } from 'types/api/txAction';
import appConfig from 'configs/app/config';
import uniswapIcon from 'icons/uniswap.svg';
import link from 'lib/link/link';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressLink from 'ui/shared/address/AddressLink';
import TokenSnippet from 'ui/shared/TokenSnippet/TokenSnippet';
......@@ -97,7 +97,7 @@ const TxDetailsAction = ({ action }: Props) => {
<Flex columnGap={ 1 } rowGap={ 2 } pl={ 3 } flexDirection="column" mt={ 2 }>
{
data.ids.map((id: string) => {
const url = link('token_instance_item', { hash: data.address, id });
const url = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: data.address, id } });
return (
<Flex key={ data.address + id } whiteSpace="pre-wrap">
<span>1 of </span>
......
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) => ({
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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