Commit 550a89c4 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #593 from blockscout/no-link

no link() and better path params: pt1
parents 1016da40 9a66d3ac
......@@ -161,6 +161,26 @@
"instanceLimit": 1
}
},
{
"type": "shell",
"command": "npx playwright show-report",
"problemMatcher": [],
"label": "pw: report",
"detail": "serve test report",
"presentation": {
"reveal": "always",
"panel": "shared",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiBlue",
"id": "output"
},
"runOptions": {
"instanceLimit": 1
}
},
// JEST TESTS
{
......
......@@ -34,8 +34,8 @@ const oldUrls = [
newPath: `${ PATHS.blocks }?tab=reorgs`,
},
{
oldPath: '/block/:id/transactions',
newPath: `${ PATHS.block }`,
oldPath: '/block/:height/transactions',
newPath: `${ PATHS.block }?tab=txs`,
},
{
oldPath: '/address/:id/transactions',
......
......@@ -97,10 +97,10 @@ export const RESOURCES = {
filterFields: [ 'type' as const ],
},
block: {
path: '/api/v2/blocks/:id',
path: '/api/v2/blocks/:height',
},
block_txs: {
path: '/api/v2/blocks/:id/transactions',
path: '/api/v2/blocks/:height/transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
......
import { useRouter } from 'next/router';
import type { Route } from 'nextjs-routes';
import React from 'react';
import appConfig from 'configs/app/config';
......@@ -14,40 +16,76 @@ import tokensIcon from 'icons/token.svg';
import transactionsIcon from 'icons/transactions.svg';
import walletIcon from 'icons/wallet.svg';
import watchlistIcon from 'icons/watchlist.svg';
import link from 'lib/link/link';
import useCurrentRoute from 'lib/link/useCurrentRoute';
import notEmpty from 'lib/notEmpty';
export default function useNavItems() {
export interface NavItem {
text: string;
nextRoute: Route;
icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
isActive?: boolean;
isNewUi?: boolean;
}
interface ReturnType {
mainNavItems: Array<NavItem>;
accountNavItems: Array<NavItem>;
profileItem: NavItem;
}
export default function useNavItems(): ReturnType {
const isMarketplaceFilled = appConfig.marketplaceAppList.length > 0;
const currentRoute = useCurrentRoute()();
const router = useRouter();
const pathname = router.pathname;
return React.useMemo(() => {
const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block'), isNewUi: true },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx'), isNewUi: true },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute.startsWith('token'), isNewUi: true },
{ text: 'Accounts', url: link('accounts'), icon: walletIcon, isActive: currentRoute === 'accounts', isNewUi: true },
{ text: 'Blocks', nextRoute: { pathname: '/blocks' as const }, icon: blocksIcon, isActive: pathname.startsWith('/block'), isNewUi: true },
{ text: 'Transactions', nextRoute: { pathname: '/txs' as const }, icon: transactionsIcon, isActive: pathname.startsWith('/tx'), isNewUi: true },
{ text: 'Tokens', nextRoute: { pathname: '/tokens' as const }, icon: tokensIcon, isActive: pathname.startsWith('/token'), isNewUi: true },
{ text: 'Accounts', nextRoute: { pathname: '/accounts' as const }, icon: walletIcon, isActive: pathname === '/accounts', isNewUi: true },
isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute.startsWith('app'), isNewUi: true } : null,
{ text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: true },
{ text: 'Apps', nextRoute: { pathname: '/apps' as const }, icon: appsIcon, isActive: pathname.startsWith('/app'), isNewUi: true } : null,
{ text: 'Charts & stats', nextRoute: { pathname: '/stats' as const }, icon: statsIcon, isActive: pathname === '/stats', isNewUi: true },
// there should be custom site sections like Stats, Faucet, More, etc but never an 'other'
// examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/
// at this stage custom menu items is under development, we will implement it later
// { text: 'Other', url: link('other'), icon: gearIcon, isActive: currentRoute === 'other' },
// { text: 'Other', url: link('other'), icon: gearIcon, isActive: pathname === 'other' },
].filter(notEmpty);
const accountNavItems = [
{ text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist', isNewUi: true },
{ text: 'Private tags', url: link('private_tags'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags'), isNewUi: true },
{ text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags', isNewUi: true },
{ text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys', isNewUi: true },
{ text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi', isNewUi: true },
{
text: 'Watchlist',
nextRoute: { pathname: '/account/watchlist' as const },
icon: watchlistIcon,
isActive: pathname === '/account/watchlist',
isNewUi: true,
},
{
text: 'Private tags',
nextRoute: { pathname: '/account/tag_address' as const },
icon: privateTagIcon,
isActive: pathname === '/account/tag_address',
isNewUi: true,
},
{
text: 'Public tags',
nextRoute: { pathname: '/account/public_tags_request' as const },
icon: publicTagIcon, isActive: pathname === '/account/public_tags_request', isNewUi: true,
},
{ text: 'API keys', nextRoute: { pathname: '/account/api_key' as const }, icon: apiKeysIcon, isActive: pathname === '/account/api_key', isNewUi: true },
{
text: 'Custom ABI',
nextRoute: { pathname: '/account/custom_abi' as const },
icon: abiIcon,
isActive: pathname === '/account/custom_abi',
isNewUi: true,
},
];
const profileItem = { text: 'My profile', url: link('profile'), icon: profileIcon, isActive: currentRoute === 'profile', isNewUi: true };
const profileItem = {
text: 'My profile', nextRoute: { pathname: '/auth/profile' as const }, icon: profileIcon, isActive: pathname === '/auth/profile', isNewUi: true };
return { mainNavItems, accountNavItems, profileItem };
}, [ isMarketplaceFilled, currentRoute ]);
}, [ isMarketplaceFilled, pathname ]);
}
......@@ -5,6 +5,9 @@ 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>,
......
......@@ -9,7 +9,7 @@
"txs": "/txs",
"tx": "/tx/:id",
"blocks": "/blocks",
"block": "/block/:id",
"block": "/block/:height",
"tokens": "/tokens",
"token_index": "/token/:hash",
"token_instance_item": "/token/:hash/instance/:id",
......
......@@ -46,13 +46,13 @@ export const ROUTES = {
},
// BLOCKS
blocks: {
pattern: PATHS.blocks,
crossNetworkNavigation: true,
},
block: {
pattern: PATHS.block,
},
// blocks: {
// pattern: PATHS.blocks,
// crossNetworkNavigation: true,
// },
// block: {
// pattern: PATHS.block,
// },
// TOKENS
tokens: {
......
import { useRouter } from 'next/router';
import React from 'react';
import type { RouteName } from 'lib/link/routes';
import { ROUTES } from 'lib/link/routes';
const PATH_PARAM_REGEXP = /\/:(\w+)/g;
export default function useCurrentRoute() {
const { route: nextRoute } = useRouter();
return React.useCallback((): RouteName => {
for (const routeName in ROUTES) {
const route = ROUTES[routeName as RouteName];
const formattedRoute = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
return `/[${ paramName }]`;
});
if (formattedRoute === nextRoute) {
return routeName as RouteName;
}
}
return 'network_index';
}, [ nextRoute ]);
}
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<'/block/[height]'>) {
const networkTitle = getNetworkTitle();
return {
title: params ? `Block ${ params.id } - ${ networkTitle }` : '',
description: params ? `View the transactions, token transfers, and uncles for block number ${ params.id }` : '',
title: params ? `Block ${ params.height } - ${ networkTitle }` : '',
description: params ? `View the transactions, token transfers, and uncles for block number ${ params.height }` : '',
};
}
export type PageParams = {
id: string;
}
......@@ -4,6 +4,7 @@ export type Props = {
cookies: string;
referrer: string;
id?: string;
height?: string;
}
export const getServerSideProps: GetServerSideProps<Props> = async({ req, query }) => {
......@@ -12,6 +13,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, query
cookies: req.headers.cookie || '',
referrer: req.headers.referer || '',
id: query.id?.toString() || '',
height: query.height?.toString() || '',
},
};
};
export default function getQueryParamString(param: string | Array<string> | undefined): string {
if (Array.isArray(param)) {
return param.join(',');
}
return param || '';
}
const withRoutes = require('nextjs-routes/config')({
outDir: 'types',
});
const path = require('path');
const headers = require('./configs/nextjs/headers');
......@@ -33,4 +36,4 @@ const moduleExports = {
output: 'standalone',
};
module.exports = moduleExports;
module.exports = withRoutes(moduleExports);
......@@ -16,7 +16,7 @@ const AppPage: NextPage = () => {
const [ isLoading, setIsLoading ] = useState(true);
const [ app, setApp ] = useState<AppItemOverview | undefined>(undefined);
const { id }: { id?: string } = router.query;
const id = router.query.id;
useEffect(() => {
if (!id) {
......
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/block/types';
import getSeo from 'lib/next/block/getSeo';
import Block from 'ui/pages/Block';
const BlockPage: NextPage<PageParams> = ({ id }: PageParams) => {
const { title, description } = getSeo({ id });
const BlockPage: NextPage<RoutedQuery<'/block/[height]'>> = ({ height }: RoutedQuery<'/block/[height]'>) => {
const { title, description } = getSeo({ height });
return (
<>
<Head>
......
import type { GetServerSideProps, NextPage } from 'next';
import Head from 'next/head';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchRedirectResult } from 'types/api/search';
......@@ -43,7 +44,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async({ req, res, r
const redirectUrl = (() => {
switch (payload.type) {
case 'block': {
return link('block', { id: q });
return route({ pathname: '/block/[height]', query: { height: q } });
}
case 'address': {
return link('address_index', { id: payload.parameter || q });
......
import './fonts.css';
import { beforeMount } from '@playwright/experimental-ct-react/hooks';
import _defaultsDeep from 'lodash/defaultsDeep';
import MockDate from 'mockdate';
import * as router from 'next/router';
const NEXT_ROUTER_MOCK = {
query: {},
pathname: '',
};
beforeMount(async({ hooksConfig }) => {
// Before mount, redefine useRouter to return mock value from test.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: I really want to redefine this property :)
router.useRouter = () => hooksConfig?.router || NEXT_ROUTER_MOCK;
router.useRouter = () => _defaultsDeep(hooksConfig?.router, NEXT_ROUTER_MOCK);
// set current date
MockDate.set('2022-11-11T12:00:00Z');
......
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// This file will be automatically regenerated when your Next.js server is running.
// nextjs-routes version: 1.0.8
/* eslint-disable */
// prettier-ignore
declare module "nextjs-routes" {
export type Route =
| StaticRoute<"/account/api_key">
| StaticRoute<"/account/custom_abi">
| StaticRoute<"/account/public_tags_request">
| StaticRoute<"/account/tag_address">
| StaticRoute<"/account/watchlist">
| StaticRoute<"/accounts">
| DynamicRoute<"/address/[id]/contract_verification", { "id": string }>
| DynamicRoute<"/address/[id]", { "id": string }>
| StaticRoute<"/api/csrf">
| StaticRoute<"/api/proxy">
| DynamicRoute<"/apps/[id]", { "id": string }>
| StaticRoute<"/apps">
| StaticRoute<"/auth/profile">
| DynamicRoute<"/block/[height]", { "height": string }>
| StaticRoute<"/blocks">
| StaticRoute<"/graph">
| StaticRoute<"/">
| StaticRoute<"/login">
| StaticRoute<"/search-results">
| StaticRoute<"/stats">
| DynamicRoute<"/token/[hash]", { "hash": string }>
| StaticRoute<"/tokens">
| DynamicRoute<"/tx/[id]", { "id": string }>
| StaticRoute<"/txs">
| StaticRoute<"/visualize/sol2uml">;
interface StaticRoute<Pathname> {
pathname: Pathname;
query?: Query | undefined;
hash?: string | null | undefined;
}
interface DynamicRoute<Pathname, Parameters> {
pathname: Pathname;
query: Parameters & Query;
hash?: string | null | undefined;
}
interface Query {
[key: string]: string | string[] | undefined;
};
export type RoutedQuery<P extends Route["pathname"]> = Extract<
Route,
{ pathname: P }
>["query"];
export type Locale = undefined;
/**
* A typesafe utility function for generating paths in your application.
*
* route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
*/
export declare function route(r: Route): string;
}
// prettier-ignore
declare module "next/link" {
import type { Route } from "nextjs-routes";
import type { LinkProps as NextLinkProps } from "next/dist/client/link";
import type {
AnchorHTMLAttributes,
DetailedReactHTMLElement,
MouseEventHandler,
PropsWithChildren,
} from "react";
export * from "next/dist/client/link";
type Query = { query?: { [key: string]: string | string[] | undefined } };
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
export interface LinkProps
extends Omit<NextLinkProps, "href" | "locale">,
AnchorHTMLAttributes<HTMLAnchorElement> {
href: Route | StaticRoute | Query;
locale?: false;
}
type LinkReactElement = DetailedReactHTMLElement<
{
onMouseEnter?: MouseEventHandler<Element> | undefined;
onClick: MouseEventHandler;
href?: string | undefined;
ref?: any;
},
HTMLElement
>;
declare function Link(props: PropsWithChildren<LinkProps>): LinkReactElement;
export default Link;
}
// prettier-ignore
declare module "next/router" {
import type { Locale, Route, RoutedQuery } from "nextjs-routes";
import type { NextRouter as Router } from "next/dist/client/router";
export * from "next/dist/client/router";
export { default } from "next/dist/client/router";
type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
type StaticRoute = Exclude<Route, { query: any }>["pathname"];
type Query = { query?: { [key: string]: string | string[] | undefined } };
interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
locale?: false;
}
export type NextRouter<P extends Route["pathname"] = Route["pathname"]> =
Extract<Route, { pathname: P }> &
Omit<
Router,
| "push"
| "replace"
| "locale"
| "locales"
| "defaultLocale"
| "domainLocales"
> & {
defaultLocale?: undefined;
domainLocales?: undefined;
locale?: Locale;
locales?: undefined;
push(
url: Route | StaticRoute | Query,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
replace(
url: Route | StaticRoute | Query,
as?: string,
options?: TransitionOptions
): Promise<boolean>;
route: P;
};
export function useRouter<P extends Route["pathname"]>(): NextRouter<P>;
}
import { Box, Flex, Text, Icon, Grid } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Address as TAddress } from 'types/api/address';
......@@ -183,7 +184,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
py={{ base: '2px', lg: 1 }}
>
<LinkInternal
href={ link('block', { id: String(data.block_number_balance_updated_at) }) }
href={ route({ pathname: '/block/[height]', query: { height: String(data.block_number_balance_updated_at) } }) }
display="flex"
alignItems="center"
>
......
import { Text, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
......@@ -7,7 +8,6 @@ import type { Block } from 'types/api/block';
import appConfig from 'configs/app/config';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobile from 'ui/shared/ListItemMobile';
import Utilization from 'ui/shared/Utilization/Utilization';
......@@ -17,7 +17,7 @@ type Props = Block & {
};
const AddressBlocksValidatedListItem = (props: Props) => {
const blockUrl = link('block', { id: String(props.height) });
const blockUrl = route({ pathname: '/block/[height]', query: { height: props.height.toString() } });
const timeAgo = useTimeAgoIncrement(props.timestamp, props.page === 1);
const totalReward = getBlockTotalReward(props);
......
import { Td, Tr, Text, Box, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import LinkInternal from 'ui/shared/LinkInternal';
import Utilization from 'ui/shared/Utilization/Utilization';
......@@ -15,7 +15,7 @@ type Props = Block & {
};
const AddressBlocksValidatedTableItem = (props: Props) => {
const blockUrl = link('block', { id: String(props.height) });
const blockUrl = route({ pathname: '/block/[height]', query: { height: String(props.height) } });
const timeAgo = useTimeAgoIncrement(props.timestamp, props.page === 1);
const totalReward = getBlockTotalReward(props);
......
import { Text, Stat, StatHelpText, StatArrow, Flex } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
......@@ -7,7 +8,6 @@ import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import appConfig from 'configs/app/config';
import { WEI, ZERO } from 'lib/consts';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -18,7 +18,7 @@ type Props = AddressCoinBalanceHistoryItem & {
};
const AddressCoinBalanceListItem = (props: Props) => {
const blockUrl = link('block', { id: String(props.block_number) });
const blockUrl = route({ pathname: '/block/[height]', query: { height: String(props.block_number) } });
const deltaBn = BigNumber(props.delta).div(WEI);
const isPositiveDelta = deltaBn.gte(ZERO);
const timeAgo = useTimeAgoIncrement(props.block_timestamp, props.page === 1);
......
import { Td, Tr, Text, Stat, StatHelpText, StatArrow } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import { WEI, ZERO } from 'lib/consts';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import link from 'lib/link/link';
import Address from 'ui/shared/address/Address';
import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -16,7 +16,7 @@ type Props = AddressCoinBalanceHistoryItem & {
};
const AddressCoinBalanceTableItem = (props: Props) => {
const blockUrl = link('block', { id: String(props.block_number) });
const blockUrl = route({ pathname: '/block/[height]', query: { height: String(props.block_number) } });
const deltaBn = BigNumber(props.delta).div(WEI);
const isPositiveDelta = deltaBn.gte(ZERO);
const timeAgo = useTimeAgoIncrement(props.block_timestamp, props.page === 1);
......
import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
......@@ -7,7 +8,6 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import appConfig from 'configs/app/config';
import eastArrowIcon from 'icons/arrows/east.svg';
import dayjs from 'lib/date/dayjs';
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';
......@@ -50,7 +50,7 @@ const TxInternalsListItem = ({
</Flex>
<HStack spacing={ 1 }>
<Text fontSize="sm" fontWeight={ 500 }>Block</Text>
<LinkInternal href={ link('block', { id: block.toString() }) }>{ block }</LinkInternal>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: block.toString() } }) }>{ block }</LinkInternal>
</HStack>
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
......
import { Tr, Td, Tag, Icon, Box, Flex, Text } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { route } from 'nextjs-routes';
import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction';
......@@ -7,7 +8,6 @@ import type { InternalTransaction } from 'types/api/internalTransaction';
import appConfig from 'configs/app/config';
import rightArrowIcon from 'icons/arrows/east.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
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';
......@@ -58,7 +58,7 @@ const AddressIntTxsTableItem = ({
</Flex>
</Td>
<Td verticalAlign="middle">
<LinkInternal href={ link('block', { id: block.toString() }) }>{ block }</LinkInternal>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: block.toString() } }) }>{ block }</LinkInternal>
</Td>
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
......
......@@ -11,7 +11,6 @@ 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 link from 'lib/link/link';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -83,7 +82,7 @@ const TokenSelect = ({ onClick }: Props) => {
}
<Tooltip label="Show all tokens">
<Box>
<NextLink href={ link('address_index', { id: addressHash }, { tab: 'tokens' }) } passHref>
<NextLink href={{ pathname: '/address/[id]', query: { id: addressHash || '', tab: 'tokens' } }} passHref>
<IconButton
aria-label="Show all tokens"
variant="outline"
......
......@@ -2,8 +2,6 @@ import { LinkOverlay } from '@chakra-ui/react';
import NextLink from 'next/link';
import React from 'react';
import link from 'lib/link/link';
type Props = {
id: string;
url: string;
......@@ -17,7 +15,7 @@ const AppLink = ({ url, external, id, title }: Props) => {
{ title }
</LinkOverlay>
) : (
<NextLink href={ link('app_index', { id: id }) } passHref>
<NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref>
<LinkOverlay>
{ title }
</LinkOverlay>
......
......@@ -2,8 +2,6 @@ import { Button } from '@chakra-ui/react';
import NextLink from 'next/link';
import React from 'react';
import link from 'lib/link/link';
type Props = {
id: string;
url: string;
......@@ -29,7 +27,7 @@ const AppModalLink = ({ url, external, id }: Props) => {
{ ...buttonProps }
>Launch app</Button>
) : (
<NextLink href={ link('app_index', { id: id }) } passHref>
<NextLink href={{ pathname: '/apps/[id]', query: { id } }} passHref>
<Button
as="a"
{ ...buttonProps }
......
......@@ -7,10 +7,10 @@ import buildApiUrl from 'playwright/utils/buildApiUrl';
import BlockDetails from './BlockDetails';
const API_URL = buildApiUrl('block', { id: '1' });
const API_URL = buildApiUrl('block', { height: '1' });
const hooksConfig = {
router: {
query: { id: '1' },
query: { height: '1' },
},
};
......
......@@ -2,6 +2,7 @@ import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router';
import { route } from 'nextjs-routes';
import React from 'react';
import { scroller, Element } from 'react-scroll';
......@@ -13,8 +14,8 @@ import getBlockReward from 'lib/block/getBlockReward';
import { WEI, WEI_IN_GWEI, ZERO } from 'lib/consts';
import dayjs from 'lib/date/dayjs';
import { space } from 'lib/html-entities';
import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import getQueryParamString from 'lib/router/getQueryParamString';
import BlockDetailsSkeleton from 'ui/block/details/BlockDetailsSkeleton';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
......@@ -30,10 +31,11 @@ import Utilization from 'ui/shared/Utilization/Utilization';
const BlockDetails = () => {
const [ isExpanded, setIsExpanded ] = React.useState(false);
const router = useRouter();
const height = getQueryParamString(router.query.height);
const { data, isLoading, isError, error } = useApiQuery('block', {
pathParams: { id: router.query.id?.toString() },
queryOptions: { enabled: Boolean(router.query.id) },
pathParams: { height },
queryOptions: { enabled: Boolean(height) },
});
const handleCutClick = React.useCallback(() => {
......@@ -46,11 +48,10 @@ const BlockDetails = () => {
const handlePrevNextClick = React.useCallback((direction: 'prev' | 'next') => {
const increment = direction === 'next' ? +1 : -1;
const nextId = String(Number(router.query.id) + increment);
const nextId = String(Number(height) + increment);
const url = link('block', { id: nextId });
router.push(url, undefined);
}, [ router ]);
router.push({ pathname: '/block/[height]', query: { height: nextId } }, undefined);
}, [ height, router ]);
if (isLoading) {
return <BlockDetailsSkeleton/>;
......@@ -116,7 +117,7 @@ const BlockDetails = () => {
title="Transactions"
hint="The number of transactions in the block."
>
<LinkInternal href={ link('block', { id: router.query.id }, { tab: 'txs' }) }>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: router.query.height?.toString() || '', tab: 'txs' } }) }>
{ data.tx_count } transactions
</LinkInternal>
</DetailsInfoItem>
......
import { Flex, Spinner, Text, Box, Icon } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
......@@ -9,7 +10,6 @@ import appConfig from 'configs/app/config';
import flameIcon from 'icons/flame.svg';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import { WEI } from 'lib/consts';
import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink';
......@@ -36,7 +36,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
{ isPending && <Spinner size="sm"/> }
<LinkInternal
fontWeight={ 600 }
href={ link('block', { id: String(data.height) }) }
href={ route({ pathname: '/block/[height]', query: { height: String(data.height) } }) }
>
{ data.height }
</LinkInternal>
......@@ -54,7 +54,7 @@ const BlocksListItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Txn</Text>
{ data.tx_count > 0 ? (
<LinkInternal href={ link('block', { id: String(data.height) }, { tab: 'txs' }) }>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: String(data.height), tab: 'txs' } }) }>
{ data.tx_count }
</LinkInternal>
) :
......
import { Tr, Td, Flex, Box, Icon, Tooltip, Spinner, useColorModeValue } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import { motion } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
......@@ -8,7 +9,6 @@ import type { Block } from 'types/api/block';
import flameIcon from 'icons/flame.svg';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import { WEI } from 'lib/consts';
import link from 'lib/link/link';
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink';
import GasUsedToTargetRatio from 'ui/shared/GasUsedToTargetRatio';
......@@ -41,7 +41,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
<Tooltip isDisabled={ data.type !== 'reorg' } label="Chain reorganizations">
<LinkInternal
fontWeight={ 600 }
href={ link('block', { id: String(data.height) }) }
href={ route({ pathname: '/block/[height]', query: { height: String(data.height) } }) }
>
{ data.height }
</LinkInternal>
......@@ -55,7 +55,7 @@ const BlocksTableItem = ({ data, isPending, enableTimeIncrement }: Props) => {
</Td>
<Td isNumeric fontSize="sm">
{ data.tx_count > 0 ? (
<LinkInternal href={ link('block', { id: String(data.height) }, { tab: 'txs' }) }>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: String(data.height), tab: 'txs' } }) }>
{ data.tx_count }
</LinkInternal>
) : data.tx_count }
......
......@@ -10,7 +10,6 @@ import type { SmartContractVerificationMethod, SmartContractVerificationConfig }
import useApiFetch from 'lib/api/useApiFetch';
import useToast from 'lib/hooks/useToast';
import link from 'lib/link/link';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
......@@ -88,7 +87,7 @@ const ContractVerificationForm = ({ method: methodFromQuery, config, hash }: Pro
variant: 'subtle',
isClosable: true,
onCloseComplete: () => {
router.push(link('address_index', { id: hash }, { tab: 'contract' }), undefined, { shallow: true });
router.push({ pathname: '/address/[id]', query: { id: hash, tab: 'contract' } }, undefined, { shallow: true });
},
});
}, [ hash, router, setError, toast ]);
......
import { Box, Heading, Flex, Text, VStack, Skeleton } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query';
import { AnimatePresence } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SocketMessage } from 'lib/socket/types';
......@@ -9,7 +10,6 @@ import type { Block } from 'types/api/block';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities';
import link from 'lib/link/link';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -98,7 +98,7 @@ const LatestBlocks = () => {
</AnimatePresence>
</VStack>
<Flex justifyContent="center">
<LinkInternal fontSize="sm" href={ link('blocks') }>View all blocks</LinkInternal>
<LinkInternal fontSize="sm" href={ route({ pathname: '/blocks' }) }>View all blocks</LinkInternal>
</Flex>
</>
);
......
......@@ -8,13 +8,13 @@ import {
Text,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Block } from 'types/api/block';
import blockIcon from 'icons/block.svg';
import getBlockTotalReward from 'lib/block/getBlockTotalReward';
import link from 'lib/link/link';
import BlockTimestamp from 'ui/blocks/BlockTimestamp';
import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
......@@ -44,7 +44,7 @@ const LatestBlocksItem = ({ block, h }: Props) => {
<HStack spacing={ 2 }>
<Icon as={ blockIcon } boxSize="30px" color="link"/>
<LinkInternal
href={ link('block', { id: String(block.height) }) }
href={ route({ pathname: '/block/[height]', query: { height: String(block.height) } }) }
fontSize="xl"
fontWeight="500"
>
......
import { Grid } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import appConfig from 'configs/app/config';
......@@ -45,7 +46,7 @@ const Stats = () => {
icon={ blockIcon }
title="Total blocks"
value={ Number(data.total_blocks).toLocaleString() }
url={ link('blocks') }
url={ route({ pathname: '/blocks' }) }
/>
{ hasAvgBlockTime && (
<StatsItem
......
......@@ -6,6 +6,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import BlockDetails from 'ui/block/BlockDetails';
import TextAd from 'ui/shared/ad/TextAd';
import Page from 'ui/shared/Page/Page';
......@@ -24,17 +25,19 @@ const BlockPageContent = () => {
const router = useRouter();
const isMobile = useIsMobile();
const appProps = useAppContext();
const height = getQueryParamString(router.query.height);
const tab = getQueryParamString(router.query.tab);
const blockTxsQuery = useQueryWithPages({
resourceName: 'block_txs',
pathParams: { id: router.query.id?.toString() },
pathParams: { height },
options: {
enabled: Boolean(router.query.id && router.query.tab === 'txs'),
enabled: Boolean(height && tab === 'txs'),
},
});
if (!router.query.id) {
return null;
if (!height) {
throw new Error('Block not found', { cause: { status: 404 } });
}
const tabs: Array<RoutedTab> = [
......@@ -42,7 +45,7 @@ const BlockPageContent = () => {
{ id: 'txs', title: 'Transactions', component: <TxsContent query={ blockTxsQuery } showBlockInfo={ false } showSocketInfo={ false }/> },
];
const hasPagination = !isMobile && router.query.tab === 'txs' && blockTxsQuery.isPaginationVisible;
const hasPagination = !isMobile && tab === 'txs' && blockTxsQuery.isPaginationVisible;
const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/blocks');
......@@ -50,7 +53,7 @@ const BlockPageContent = () => {
<Page>
<TextAd mb={ 6 }/>
<PageTitle
text={ `Block #${ router.query.id }` }
text={ `Block #${ height }` }
backLinkUrl={ hasGoBackLink ? appProps.referrer : undefined }
backLinkLabel="Back to blocks list"
/>
......
......@@ -7,7 +7,7 @@ import type { SmartContractVerificationConfigRaw, SmartContractVerificationMetho
import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext';
import isBrowser from 'lib/isBrowser';
import link from 'lib/link/link';
import getQueryParamString from 'lib/router/getQueryParamString';
import ContractVerificationForm from 'ui/contractVerification/ContractVerificationForm';
import { isValidVerificationMethod, sortVerificationMethods } from 'ui/contractVerification/utils';
import Address from 'ui/shared/address/Address';
......@@ -26,8 +26,8 @@ const ContractVerification = () => {
const hasGoBackLink = referrer && referrer.includes('/address');
const router = useRouter();
const hash = router.query.id?.toString();
const method = router.query.id?.toString() as SmartContractVerificationMethod | undefined;
const hash = getQueryParamString(router.query.id);
const method = getQueryParamString(router.query.method) as SmartContractVerificationMethod;
const contractQuery = useApiQuery('contract', {
pathParams: { id: hash },
......@@ -55,7 +55,7 @@ const ContractVerification = () => {
React.useEffect(() => {
if (method && hash) {
router.replace(link('address_contract_verification', { id: hash }), undefined, { scroll: false, shallow: true });
router.replace({ pathname: '/address/[id]/contract_verification', query: { id: 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(link('address_index', { id: hash }, { tab: 'contract' }), undefined, { scroll: false, shallow: true });
router.push({ pathname: '/address/[id]', query: { id: hash, tab: 'contract' } }, undefined, { scroll: false, shallow: true });
}
}, [ hash, isVerifiedContract, router ]);
......
import { Text, Flex, Icon, Box, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
......@@ -56,7 +57,7 @@ const SearchResultListItem = ({ data, searchTerm }: Props) => {
return (
<Flex alignItems="center">
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<LinkInternal fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }>
<LinkInternal fontWeight={ 700 } href={ route({ pathname: '/block/[height]', query: { height: String(data.block_number) } }) }>
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
</LinkInternal>
</Flex>
......
import { Tr, Td, Text, Flex, Icon, Box } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
......@@ -88,7 +89,7 @@ const SearchResultTableItem = ({ data, searchTerm }: Props) => {
<Td fontSize="sm">
<Flex alignItems="center">
<Icon as={ blockIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
<LinkInternal fontWeight={ 700 } href={ link('block', { id: String(data.block_number) }) }>
<LinkInternal fontWeight={ 700 } href={ route({ pathname: '/block/[height]', query: { height: String(data.block_number) } }) }>
<Box as={ shouldHighlightHash ? 'span' : 'mark' }>{ data.block_number }</Box>
</LinkInternal>
</Flex>
......
import type { LinkProps } from '@chakra-ui/react';
import { Link } from '@chakra-ui/react';
import type { LinkProps as NextLinkProps } from 'next/link';
import NextLink from 'next/link';
import type { LegacyRef } from 'react';
import React from 'react';
......@@ -11,7 +12,7 @@ const LinkInternal = (props: LinkProps, ref: LegacyRef<HTMLAnchorElement>) => {
}
return (
<NextLink href={ props.href } passHref target={ props.target }>
<NextLink href={ props.href as NextLinkProps['href'] } passHref target={ props.target }>
<Link { ...props } ref={ ref }/>
</NextLink>
);
......
......@@ -52,7 +52,7 @@ const RoutedTabs = ({ tabs, tabListProps, rightSlot, stickyEnabled, className, .
const nextTab = tabs[index];
router.push(
{ pathname: router.asPath.split('?')[0], query: { tab: nextTab.id } },
{ pathname: router.asPath.split('?')[0], query: { tab: nextTab.id } } as Parameters<typeof router.push>[0],
undefined,
{ shallow: true },
);
......
import { chakra, shouldForwardProp, Tooltip, Box } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import type { HTMLAttributeAnchorTarget } from 'react';
import React from 'react';
......@@ -27,7 +28,7 @@ type AddressTokenTxProps = {
type BlockProps = {
type: 'block';
hash: string;
id: string;
height: string;
}
type AddressTokenProps = {
......@@ -48,7 +49,7 @@ const AddressLink = (props: Props) => {
} else if (type === 'token') {
url = link('token_index', { hash: hash });
} else if (type === 'block') {
url = link('block', { id: props.id });
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' });
} else {
......
import { Link, Icon, Text, HStack, Tooltip, Box } from '@chakra-ui/react';
import NextLink from 'next/link';
import { route } from 'nextjs-routes';
import React from 'react';
import type { NavItem } from 'lib/hooks/useNavItems';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import useColors from './useColors';
interface Props {
type Props = NavItem & {
isCollapsed?: boolean;
isActive?: boolean;
url: string;
text: string;
icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
px?: string | number;
isNewUi: boolean;
}
const NavLink = ({ text, url, icon, isCollapsed, isActive, px, isNewUi }: Props) => {
const NavLink = ({ text, nextRoute, icon, isCollapsed, isActive, px, isNewUi }: Props) => {
const colors = useColors();
const isExpanded = isCollapsed === false;
const content = (
<Link
{ ...(isNewUi ? {} : { href: url }) }
{ ...(isNewUi ? {} : { href: route(nextRoute) }) }
w={{ base: '100%', lg: isExpanded ? '180px' : '60px', xl: isCollapsed ? '60px' : '180px' }}
px={ px || { base: 3, lg: isExpanded ? 3 : '15px', xl: isCollapsed ? '15px' : 3 } }
py={ 2.5 }
......@@ -68,7 +65,7 @@ const NavLink = ({ text, url, icon, isCollapsed, isActive, px, isNewUi }: Props)
{ /* why not NextLink in all cases? since prev UI and new one are hosting in the same domain and global routing is managed by nginx */ }
{ /* we have to hard reload page on every transition between urls from different part of the app */ }
{ isNewUi ? (
<NextLink href={ url } passHref>
<NextLink href={ nextRoute } passHref>
{ content }
</NextLink>
) : content }
......
......@@ -12,6 +12,7 @@ const hooksConfig = {
router: {
route: '/blocks',
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' },
pathname: '/blocks',
},
};
......
......@@ -12,6 +12,7 @@ test('no auth', async({ mount, page }) => {
const hooksConfig = {
router: {
asPath: '/',
pathname: '/',
},
};
const component = await mount(
......
import { chakra, Text, Flex, useColorModeValue, Icon, Box } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { SearchResultItem } from 'types/api/search';
......@@ -32,7 +33,7 @@ const SearchBarSuggestItem = ({ data, isMobile, searchTerm }: Props) => {
return link('tx', { id: data.tx_hash });
}
case 'block': {
return link('block', { id: String(data.block_number) });
return route({ pathname: '/block/[height]', query: { height: String(data.block_number) } });
}
}
})();
......
......@@ -8,7 +8,6 @@ import type { TokenInfo } from 'types/api/token';
import useApiQuery from 'lib/api/useApiQuery';
import getCurrencyValue from 'lib/getCurrencyValue';
import link from 'lib/link/link';
import type { TokenTabs } from 'ui/pages/Token';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSkeletonRow from 'ui/shared/skeletons/DetailsSkeletonRow';
......@@ -26,9 +25,8 @@ const TokenDetails = ({ tokenQuery }: Props) => {
});
const changeUrlAndScroll = useCallback((tab: TokenTabs) => () => {
const newLink = link('token_index', { hash: router.query.hash }, { tab: tab });
router.push(
newLink,
{ pathname: '/token/[hash]', query: { hash: router.query.hash?.toString() || '', tab } },
undefined,
{ shallow: true },
);
......
......@@ -5,6 +5,7 @@ import {
Icon,
Text,
} from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
......@@ -14,7 +15,6 @@ import rightArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg';
import getValueWithUnit from 'lib/getValueWithUnit';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
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';
......@@ -88,7 +88,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
{ showBlockInfo && tx.block !== null && (
<Box mt={ 2 }>
<Text as="span">Block </Text>
<LinkInternal href={ link('block', { id: tx.block.toString() }) }>{ tx.block }</LinkInternal>
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height: tx.block.toString() } }) }>{ tx.block }</LinkInternal>
</Box>
) }
<Flex alignItems="center" height={ 6 } mt={ 6 }>
......
......@@ -10,13 +10,13 @@ import {
Hide,
} from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { route } from 'nextjs-routes';
import React from 'react';
import type { Transaction } from 'types/api/transaction';
import rightArrowIcon from 'icons/arrows/east.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
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';
......@@ -99,7 +99,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
</Td>
{ showBlockInfo && (
<Td>
{ tx.block && <LinkInternal href={ link('block', { id: tx.block.toString() }) }>{ tx.block }</LinkInternal> }
{ tx.block && <LinkInternal href={ route({ pathname: '/block/[height]', query: { height: tx.block.toString() } }) }>{ tx.block }</LinkInternal> }
</Td>
) }
<Show above="xl" ssr={ false }>
......
......@@ -4897,6 +4897,14 @@ anymatch@^3.0.3:
normalize-path "^3.0.0"
picomatch "^2.0.4"
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
aproba@^1.0.3:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
......@@ -5161,6 +5169,11 @@ bignumber.js@^9.1.0:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.0.tgz#8d340146107fe3a6cb8d40699643c302e8773b62"
integrity sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
bind-decorator@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/bind-decorator/-/bind-decorator-1.0.11.tgz#e41bc06a1f65dd9cec476c91c5daf3978488252f"
......@@ -5222,7 +5235,7 @@ brace-expansion@^2.0.1:
dependencies:
balanced-match "^1.0.0"
braces@^3.0.2:
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
......@@ -5415,6 +5428,21 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
ci-info@^3.2.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.6.1.tgz#7594f1c95cb7fdfddee7af95a13af7dbc67afdcf"
......@@ -7421,7 +7449,7 @@ get-symbol-description@^1.0.0:
call-bind "^1.0.2"
get-intrinsic "^1.1.1"
glob-parent@^5.1.2:
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
......@@ -7778,6 +7806,13 @@ is-bigint@^1.0.1:
dependencies:
has-bigints "^1.0.1"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-boolean-object@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
......@@ -7844,7 +7879,7 @@ is-generator-function@^1.0.7:
dependencies:
has-tostringtag "^1.0.0"
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
......@@ -9039,6 +9074,13 @@ next@12.2.5:
"@next/swc-win32-ia32-msvc" "12.2.5"
"@next/swc-win32-x64-msvc" "12.2.5"
nextjs-routes@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/nextjs-routes/-/nextjs-routes-1.0.8.tgz#7604fe12dd0132c5a4c61e8d4a6c00c19cf615fb"
integrity sha512-EWBrzT0VS+SGcrhEbCUGnDlEAS6tjJSiecbSA7x75pBbcW9IYkYw7JxDMNqFhiSrvtYAL8yIWFwbQ8x0Jmz7wg==
dependencies:
chokidar "^3.5.3"
node-addon-api@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
......@@ -9080,7 +9122,7 @@ node-releases@^2.0.6:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
normalize-path@^3.0.0:
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
......@@ -9386,7 +9428,7 @@ picocolors@^1.0.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1:
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
......@@ -9931,6 +9973,13 @@ readable-stream@^4.0.0:
events "^3.3.0"
process "^0.11.10"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
real-require@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381"
......
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