Commit 036dcde5 authored by tom's avatar tom

network menu and rewrite prev implementation

parent 3d0f21f8
......@@ -20,16 +20,16 @@ export default function useNavItems() {
return React.useMemo(() => {
const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: [ 'block', 'blocks' ].includes(currentRoute) },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: [ 'tx', 'txs' ].includes(currentRoute) },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: [ 'token', 'tokens' ].includes(currentRoute) },
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute === 'blocks' },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' },
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' },
{ text: 'Other', url: link('other'), icon: gearIcon, isActive: currentRoute === 'other' },
];
const accountNavItems = [
{ text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist' },
{ text: 'Private tags', url: link('private_tags', { tab: 'address' }), icon: privateTagIcon, isActive: currentRoute === 'private_tags' },
{ text: 'Private tags', url: link('private_tags_address'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags') },
{ text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags' },
{ text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys' },
{ text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi' },
......
export const ACCOUNT_ROUTES: Array<RouteName> = [ 'watchlist', 'private_tags_address', 'private_tags_tx', 'public_tags', 'api_keys', 'custom_abi' ];
import type { RouteName } from 'lib/link/routes';
export default function isAccountRoute(route: RouteName) {
return ACCOUNT_ROUTES.includes(route);
}
import { compile } from 'path-to-regexp';
import { ROUTES } from './routes';
import type { RouteName } from './routes';
const PATH_PARAM_REGEXP = /\/\[(\w+)\]/g;
export function link(routeName: RouteName, urlParams?: Record<string, string | undefined>, queryParams?: Record<string, string>): string {
const route = ROUTES[routeName];
if (!route) {
return '';
}
const toPath = compile(route.pattern, { encode: encodeURIComponent, validate: false });
const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
const paramValue = urlParams?.[paramName];
return paramValue ? `/${ paramValue }` : '';
});
const path = toPath(urlParams);
const url = new URL(path, window.location.origin);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
......
export interface Route {
pattern: string;
crossNetworkNavigation?: boolean; // route will not change when switching networks
}
export type RouteName = keyof typeof ROUTES;
const BASE_PATH = '/:network_type/:network_sub_type';
const BASE_PATH = '/[network_type]/[network_sub_type]';
export const ROUTES = {
tx: {
pattern: `${ BASE_PATH }/tx/:id/:tab?`,
},
txs: {
pattern: `${ BASE_PATH }/txs`,
},
blocks: {
pattern: `${ BASE_PATH }/blocks`,
},
tokens: {
pattern: `${ BASE_PATH }/tokens`,
},
apps: {
pattern: `${ BASE_PATH }/apps`,
},
// ??? what URL will be here
other: {
pattern: `${ BASE_PATH }/other`,
// NETWORK MAIN PAGE
network_index: {
pattern: `${ BASE_PATH }`,
crossNetworkNavigation: true,
},
// ACCOUNT
watchlist: {
pattern: `${ BASE_PATH }/account/watchlist`,
crossNetworkNavigation: true,
},
private_tags: {
pattern: `${ BASE_PATH }/account/tag_{:tab}`,
private_tags_address: {
pattern: `${ BASE_PATH }/account/tag_address`,
crossNetworkNavigation: true,
},
private_tags_tx: {
pattern: `${ BASE_PATH }/account/tag_transaction`,
crossNetworkNavigation: true,
},
public_tags: {
pattern: `${ BASE_PATH }/account/public_tags_request`,
crossNetworkNavigation: true,
},
api_keys: {
pattern: `${ BASE_PATH }/account/api_key`,
crossNetworkNavigation: true,
},
custom_abi: {
pattern: `${ BASE_PATH }/account/custom_abi`,
crossNetworkNavigation: true,
},
profile: {
pattern: `${ BASE_PATH }/auth/profile`,
crossNetworkNavigation: true,
},
// TRANSACTIONS
txs: {
pattern: `${ BASE_PATH }/txs`,
crossNetworkNavigation: true,
},
tx_index: {
pattern: `${ BASE_PATH }/tx/[id]`,
},
tx_internal: {
pattern: `${ BASE_PATH }/tx/[id]/internal-transactions`,
},
tx_logs: {
pattern: `${ BASE_PATH }/tx/[id]/logs`,
},
tx_raw_trace: {
pattern: `${ BASE_PATH }/tx/[id]/raw-trace`,
},
tx_state: {
pattern: `${ BASE_PATH }/tx/[id]/state`,
},
// BLOCKS
blocks: {
pattern: `${ BASE_PATH }/blocks`,
crossNetworkNavigation: true,
},
// TOKENS
tokens: {
pattern: `${ BASE_PATH }/tokens`,
crossNetworkNavigation: true,
},
// APPS
apps: {
pattern: `${ BASE_PATH }/apps`,
},
// ??? what URL will be here
other: {
pattern: `${ BASE_PATH }/other`,
},
};
......
import { useRouter } from 'next/router';
import { match } from 'path-to-regexp';
import React from 'react';
import type { RouteName } from 'lib/link/routes';
import { ROUTES } from 'lib/link/routes';
export default function useCurrentRoute() {
const { asPath } = useRouter();
const { route: nextRoute } = useRouter();
return React.useCallback(() => {
return React.useCallback((): RouteName => {
for (const routeName in ROUTES) {
const route = ROUTES[routeName as RouteName];
const isMatch = Boolean(match(route.pattern)(asPath));
if (isMatch) {
if (route.pattern === nextRoute) {
return routeName as RouteName;
}
}
return '';
}, [ asPath ]);
return 'network_index';
}, [ nextRoute ]);
}
export const ACCOUNT_ROUTES = [ '/watchlist', '/tag_address', '/tag_transaction', '/public_tags_request', '/api_key', '/custom_abi' ];
export default function isAccountRoute(route: string) {
return ACCOUNT_ROUTES.includes(route);
}
import { useRouter } from 'next/router';
import React from 'react';
import useNetwork from 'lib/hooks/useNetwork';
import isAccountRoute from 'lib/link/isAccountRoute';
import { link } from 'lib/link/link';
import { ROUTES } from 'lib/link/routes';
import useCurrentRoute from 'lib/link/useCurrentRoute';
import NETWORKS from 'lib/networks/availableNetworks';
export default function useNetworkNavigationItems() {
const selectedNetwork = useNetwork();
const currentRouteName = useCurrentRoute()();
const currentRoute = ROUTES[currentRouteName];
const router = useRouter();
const isAccount = isAccountRoute(currentRouteName);
return React.useMemo(() => {
return NETWORKS.map((network) => {
const routeName = (() => {
if ('crossNetworkNavigation' in currentRoute && currentRoute.crossNetworkNavigation) {
if ((isAccount && network.isAccountSupported) || !isAccount) {
return currentRouteName;
}
}
return 'network_index';
})();
const url = link(routeName, { ...router.query, network_type: network.type, network_sub_type: network.subType });
return {
...network,
url: url,
isActive: selectedNetwork?.type === network.type && selectedNetwork?.subType === network?.subType,
};
});
}, [ currentRoute, currentRouteName, isAccount, router.query, selectedNetwork?.subType, selectedNetwork?.type ]);
}
import { PopoverContent, PopoverBody, Text, Tabs, TabList, TabPanels, TabPanel, Tab, VStack } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { NetworkGroup } from 'types/networks';
import useNetwork from 'lib/hooks/useNetwork';
import NETWORKS from 'lib/networks/availableNetworks';
import useNetworkNavigationItems from 'lib/networks/useNetworkNavigationItems';
import NetworkMenuLink from './NetworkMenuLink';
......@@ -13,9 +13,8 @@ const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ];
const availableTabs = TABS.filter((tab) => NETWORKS.some(({ group }) => group === tab));
const NetworkMenuPopup = () => {
const router = useRouter();
const routeName = router.pathname.replace('/[network_type]/[network_sub_type]', '');
const selectedNetwork = useNetwork();
const items = useNetworkNavigationItems();
const selectedTab = availableTabs.findIndex((tab) => selectedNetwork?.group === tab);
return (
......@@ -32,14 +31,12 @@ const NetworkMenuPopup = () => {
{ availableTabs.map((tab) => (
<TabPanel key={ tab } p={ 0 }>
<VStack as="ul" spacing={ 2 } alignItems="stretch" mt={ 4 }>
{ NETWORKS
{ items
.filter((network) => network.group === tab)
.map((network) => (
<NetworkMenuLink
key={ network.name }
{ ...network }
isActive={ network.name === selectedNetwork?.name }
routeName={ routeName }
/>
)) }
</VStack>
......
import { Box, Select, VStack } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router';
import React from 'react';
import type { NetworkGroup } from 'types/networks';
import useNetwork from 'lib/hooks/useNetwork';
import NETWORKS from 'lib/networks/availableNetworks';
import useNetworkNavigationItems from 'lib/networks/useNetworkNavigationItems';
import NetworkMenuLink from './NetworkMenuLink';
const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ];
const NetworkMenuContentMobile = () => {
const router = useRouter();
const routeName = router.pathname.replace('/[network_type]/[network_sub_type]', '');
const selectedNetwork = useNetwork();
const [ selectedTab, setSelectedTab ] = React.useState<NetworkGroup>(TABS.find((tab) => selectedNetwork?.group === tab) || 'mainnets');
const items = useNetworkNavigationItems();
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedTab(event.target.value as NetworkGroup);
......@@ -28,14 +26,12 @@ const NetworkMenuContentMobile = () => {
{ TABS.map((tab) => <option key={ tab } value={ tab }>{ capitalize(tab) }</option>) }
</Select>
<VStack as="ul" spacing={ 2 } alignItems="stretch" mt={ 6 }>
{ NETWORKS
{ items
.filter(({ group }) => group === selectedTab)
.map((network) => (
<NetworkMenuLink
key={ network.name }
{ ...network }
isActive={ network.name === selectedNetwork?.name }
routeName={ routeName }
isMobile
/>
))
......
......@@ -6,34 +6,36 @@ import type { Network } from 'types/networks';
import checkIcon from 'icons/check.svg';
import placeholderIcon from 'icons/networks/icons/placeholder.svg';
import isAccountRoute from 'lib/networks/isAccountRoute';
import useColors from './useColors';
interface Props extends Network {
isActive: boolean;
isMobile?: boolean;
routeName: string;
url: string;
}
const NetworkMenuLink = ({ name, type, subType, icon, isActive, isMobile, routeName, isAccountSupported }: Props) => {
const isAccount = isAccountRoute(routeName);
const localPath = (() => {
if (isAccount && isAccountSupported) {
return routeName;
}
const NetworkMenuLink = ({ name, type, subType, icon, isActive, isMobile, url }: Props) => {
// const isAccount = isAccountRoute(routeName);
// const localPath = (() => {
// if (isAccount && isAccountSupported) {
// return routeName;
// }
if (isAccount && !isAccountSupported) {
return '';
}
// if (isAccount && !isAccountSupported) {
// return '';
// }
// will change when blocks&transaction is implemented
return routeName;
})();
const pathName = `/${ type }${ subType ? '/' + subType : '' }${ localPath }`;
// // will change when blocks&transaction is implemented
// return routeName;
// })();
// const pathName = `/${ type }${ subType ? '/' + subType : '' }${ localPath }`;
// const url = link(routeName, { network_type: type, network_sub_type: subType });
// console.log('__>__', url);
// will fix later after we agree on CI/CD workflow
const href = type === 'xdai' && subType === 'testnet' ? pathName : 'https://blockscout.com' + pathName;
// const href = type === 'xdai' && subType === 'testnet' ? pathName : 'https://blockscout.com' + pathName;
const hasIcon = Boolean(icon);
const colors = useColors({ hasIcon });
......@@ -49,7 +51,7 @@ const NetworkMenuLink = ({ name, type, subType, icon, isActive, isMobile, routeN
return (
<Box as="li" listStyleType="none">
<NextLink href={ href } passHref>
<NextLink href={ url } passHref>
<Flex
as="a"
px={ isMobile ? 3 : 4 }
......
......@@ -8,6 +8,7 @@ import {
import { useRouter } from 'next/router';
import React from 'react';
import type { RouteName } from 'lib/link/routes';
import useLink from 'lib/link/useLink';
import Page from 'ui/shared/Page';
import PageHeader from 'ui/shared/PageHeader';
......@@ -20,14 +21,15 @@ interface Tab {
name: string;
path?: string;
component?: React.ReactNode;
routeName: RouteName;
}
const TABS: Array<Tab> = [
{ type: 'details', name: 'Details', component: <TxDetails/> },
{ type: 'internal_txn', path: 'internal-transactions', name: 'Internal txn', component: <TxInternals/> },
{ type: 'logs', path: 'logs', name: 'Logs', component: <TxLogs/> },
{ type: 'state', path: 'state', name: 'State' },
{ type: 'raw_trace', path: 'raw-trace', name: 'Raw trace' },
{ type: 'details', routeName: 'tx_index', name: 'Details', component: <TxDetails/> },
{ type: 'internal_txn', routeName: 'tx_internal', name: 'Internal txn', component: <TxInternals/> },
{ type: 'logs', routeName: 'tx_logs', name: 'Logs', component: <TxLogs/> },
{ type: 'state', routeName: 'tx_state', name: 'State' },
{ type: 'raw_trace', routeName: 'tx_raw_trace', name: 'Raw trace' },
];
export interface Props {
......@@ -42,7 +44,7 @@ const TransactionPageContent = ({ tab }: Props) => {
const handleTabChange = React.useCallback((index: number) => {
const nextTab = TABS[index];
setActiveTab(nextTab.type);
const newUrl = link('tx', { id: router.query.id as string, tab: nextTab.path });
const newUrl = link(nextTab.routeName, { id: router.query.id as string });
window.history.replaceState(history.state, '', newUrl);
}, [ setActiveTab, link, router.query.id ]);
......
......@@ -4497,11 +4497,6 @@ path-parse@^1.0.6, path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@^6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
......
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