Commit 43feeff6 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into search-bar

parents 0c3702ee c0f406be
...@@ -44,6 +44,8 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__ ...@@ -44,6 +44,8 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__ NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__ NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__ NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_API_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PROTOCOL__
NEXT_PUBLIC_API_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PORT__
# external services config # external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__ NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
...@@ -27,6 +27,14 @@ const baseUrl = [ ...@@ -27,6 +27,14 @@ const baseUrl = [
].filter(Boolean).join(''); ].filter(Boolean).join('');
const authUrl = getEnvValue(process.env.NEXT_PUBLIC_AUTH_URL) || baseUrl; const authUrl = getEnvValue(process.env.NEXT_PUBLIC_AUTH_URL) || baseUrl;
const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST); const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST);
const apiSchema = getEnvValue(process.env.NEXT_PUBLIC_API_PROTOCOL) || 'https';
const apiPort = getEnvValue(process.env.NEXT_PUBLIC_API_PORT);
const apiEndpoint = apiHost ? [
apiSchema || 'https',
'://',
apiHost,
apiPort && ':' + apiPort,
].filter(Boolean).join('') : 'https://blockscout.com';
const logoutUrl = (() => { const logoutUrl = (() => {
try { try {
...@@ -91,7 +99,7 @@ const config = Object.freeze({ ...@@ -91,7 +99,7 @@ const config = Object.freeze({
}, },
api: { api: {
host: apiHost, host: apiHost,
endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com', endpoint: apiEndpoint,
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com', socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''), basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
}, },
......
This diff is collapsed.
...@@ -2,10 +2,11 @@ import { compile } from 'path-to-regexp'; ...@@ -2,10 +2,11 @@ import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import type { ApiResource } from './resources'; import { RESOURCES } from './resources';
import type { ApiResource, ResourceName } from './resources';
export default function buildUrl( export default function buildUrl(
resource: ApiResource, _resource: ApiResource | ResourceName,
pathParams?: Record<string, string | undefined>, pathParams?: Record<string, string | undefined>,
queryParams?: Record<string, string | number | undefined>, queryParams?: Record<string, string | number | undefined>,
) { ) {
...@@ -22,6 +23,7 @@ export default function buildUrl( ...@@ -22,6 +23,7 @@ export default function buildUrl(
// will need to change the condition if there are more micro services that need authentication and DB state changes // will need to change the condition if there are more micro services that need authentication and DB state changes
const needProxy = appConfig.host !== appConfig.api.host; const needProxy = appConfig.host !== appConfig.api.host;
const resource: ApiResource = typeof _resource === 'string' ? RESOURCES[_resource] : _resource;
const baseUrl = needProxy ? appConfig.baseUrl : (resource.endpoint || appConfig.api.endpoint); const baseUrl = needProxy ? appConfig.baseUrl : (resource.endpoint || appConfig.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath; const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = needProxy ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path; const path = needProxy ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
......
import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config';
import { RESOURCES } from './resources';
import type { ApiResource, ResourceName } from './resources';
export default function buildUrlNode(
_resource: ApiResource | ResourceName,
pathParams?: Record<string, string | undefined>,
queryParams?: Record<string, string | number | undefined>,
) {
const resource: ApiResource = typeof _resource === 'string' ? RESOURCES[_resource] : _resource;
const baseUrl = resource.endpoint || appConfig.api.endpoint;
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = basePath + resource.path;
const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
value && url.searchParams.append(key, String(value));
});
return url.toString();
}
import type { NextApiRequest } from 'next';
export default function getSearchParams(req: NextApiRequest) {
const searchParams: Record<string, string> = {};
Object.entries(req.query).forEach(([ key, value ]) => {
searchParams[key] = Array.isArray(value) ? value.join(',') : (value || '');
});
return new URLSearchParams(searchParams).toString();
}
import type { NextApiRequest } from 'next';
import appConfig from 'configs/app/config';
export default function getUrlWithNetwork(_req: NextApiRequest, path: string) {
return [
appConfig.api.basePath,
path,
]
.filter((segment) => segment !== '' && segment !== '/')
.join('');
}
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch';
import getUrlWithNetwork from 'lib/api/getUrlWithNetwork';
import { httpLogger } from 'lib/api/logger';
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE';
export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>, apiEndpoint?: string) {
const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
httpLogger(_req, res);
if (!_req.method || !allowedMethods.includes(_req.method as Methods)) {
res.setHeader('Allow', allowedMethods);
res.status(405).end(`Method ${ _req.method } Not Allowed`);
return;
}
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
const url = apiEndpoint ? `/api${ getUrl(_req) }` : getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req, apiEndpoint);
const response = await fetch(url, {
method: _req.method,
body: isBodyDisallowed ? undefined : _req.body,
});
if (response.status === 200) {
const data = await response.json();
res.status(200).json(data);
return;
}
let responseError;
const defaultError = { statusText: response.statusText, status: response.status };
try {
const error = await response.json() as { errors: unknown };
responseError = error?.errors || defaultError;
} catch (error) {
responseError = defaultError;
}
httpLogger.logger.error({ err: responseError, url: _req.url });
res.status(500).json(responseError);
};
return handler;
}
...@@ -2,25 +2,20 @@ import type { NextApiRequest } from 'next'; ...@@ -2,25 +2,20 @@ import type { NextApiRequest } from 'next';
import type { RequestInit, Response } from 'node-fetch'; import type { RequestInit, Response } from 'node-fetch';
import nodeFetch from 'node-fetch'; import nodeFetch from 'node-fetch';
import appConfig from 'configs/app/config';
import { httpLogger } from 'lib/api/logger'; import { httpLogger } from 'lib/api/logger';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
export default function fetchFactory( export default function fetchFactory(
_req: NextApiRequest, _req: NextApiRequest,
apiEndpoint: string = appConfig.api.endpoint,
) { ) {
return function fetch(path: string, init?: RequestInit): Promise<Response> { // first arg can be only a string
const csrfToken = _req.headers['x-csrf-token']?.toString(); // FIXME migrate to RequestInfo later if needed
return function fetch(url: string, init?: RequestInit): Promise<Response> {
const headers = { const headers = {
accept: 'application/json', accept: 'application/json',
'content-type': 'application/json', 'content-type': 'application/json',
cookie: `${ cookies.NAMES.API_TOKEN }=${ _req.cookies[cookies.NAMES.API_TOKEN] }`, cookie: `${ cookies.NAMES.API_TOKEN }=${ _req.cookies[cookies.NAMES.API_TOKEN] }`,
...(csrfToken ? { 'x-csrf-token': csrfToken } : {}),
}; };
const url = new URL(path, apiEndpoint);
httpLogger.logger.info({ httpLogger.logger.info({
message: 'Trying to call API', message: 'Trying to call API',
...@@ -28,7 +23,7 @@ export default function fetchFactory( ...@@ -28,7 +23,7 @@ export default function fetchFactory(
req: _req, req: _req,
}); });
return nodeFetch(url.toString(), { return nodeFetch(url, {
headers, headers,
...init, ...init,
}); });
......
...@@ -36,6 +36,9 @@ export interface ApiResource { ...@@ -36,6 +36,9 @@ export interface ApiResource {
export const RESOURCES = { export const RESOURCES = {
// ACCOUNT // ACCOUNT
csrf: {
path: '/api/account/v1/get_csrf',
},
user_info: { user_info: {
path: '/api/account/v1/user/info', path: '/api/account/v1/user/info',
}, },
...@@ -216,7 +219,6 @@ export type ResourcePaginationKey<R extends ResourceName> = typeof RESOURCES[R] ...@@ -216,7 +219,6 @@ export type ResourcePaginationKey<R extends ResourceName> = typeof RESOURCES[R]
export const resourceKey = (x: keyof typeof RESOURCES) => x; export const resourceKey = (x: keyof typeof RESOURCES) => x;
export interface ResourceError<T = unknown> { export interface ResourceError<T = unknown> {
error?: T;
payload?: T; payload?: T;
status: Response['status']; status: Response['status'];
statusText: Response['statusText']; statusText: Response['statusText'];
......
...@@ -5,6 +5,7 @@ import React from 'react'; ...@@ -5,6 +5,7 @@ import React from 'react';
import type { CsrfData } from 'types/client/account'; import type { CsrfData } from 'types/client/account';
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
import { getResourceKey } from 'lib/api/useApiQuery';
export interface Params { export interface Params {
method?: RequestInit['method']; method?: RequestInit['method'];
...@@ -16,17 +17,20 @@ export interface Params { ...@@ -16,17 +17,20 @@ export interface Params {
export default function useFetch() { export default function useFetch() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { token } = queryClient.getQueryData<CsrfData>([ 'csrf' ]) || {}; const { token } = queryClient.getQueryData<CsrfData>(getResourceKey('csrf')) || {};
return React.useCallback(<Success, Error>(path: string, params?: Params): Promise<Success | ResourceError<Error>> => { return React.useCallback(<Success, Error>(path: string, params?: Params): Promise<Success | ResourceError<Error>> => {
const reqParams = { const reqParams = {
...params, ...params,
body: params?.method && params?.body && ![ 'GET', 'HEAD' ].includes(params.method) ? body: params?.method && params?.body && ![ 'GET', 'HEAD' ].includes(params.method) ?
JSON.stringify(params.body) : JSON.stringify({
...params.body,
_csrf_token: token,
}) :
undefined, undefined,
headers: { headers: {
...params?.headers, ...params?.headers,
...(token ? { 'x-csrf-token': token } : {}), // ...(token ? { 'x-csrf-token': token } : {}),
}, },
}; };
...@@ -40,8 +44,6 @@ export default function useFetch() { ...@@ -40,8 +44,6 @@ export default function useFetch() {
return response.json().then( return response.json().then(
(jsonError) => Promise.reject({ (jsonError) => Promise.reject({
// DEPRECATED
error: jsonError as Error,
payload: jsonError as Error, payload: jsonError as Error,
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"dev:local": "./node_modules/.bin/dotenv -e ./configs/envs/.env.localhost -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty", "dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"dev:goerli": "./node_modules/.bin/dotenv -e ./configs/envs/.env.goerli -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty", "dev:goerli": "./node_modules/.bin/dotenv -e ./configs/envs/.env.goerli -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"build": "next build", "build": "next build",
......
...@@ -23,7 +23,7 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -23,7 +23,7 @@ function MyApp({ Component, pageProps }: AppProps) {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
retry: (failureCount, _error) => { retry: (failureCount, _error) => {
const error = _error as ResourceError<{ status: number }>; const error = _error as ResourceError<{ status: number }>;
const status = error?.status || error?.error?.status; const status = error?.status || error?.payload?.status;
if (status && status >= 400 && status < 500) { if (status && status >= 400 && status < 500) {
// don't do retry for client error responses // don't do retry for client error responses
return false; return false;
......
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch'; import buildUrlNode from 'lib/api/buildUrlNode';
import getUrlWithNetwork from 'lib/api/getUrlWithNetwork';
import { httpLogger } from 'lib/api/logger'; import { httpLogger } from 'lib/api/logger';
import fetchFactory from 'lib/api/nodeFetch';
export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) { export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) {
httpLogger(_req, res); httpLogger(_req, res);
const url = getUrlWithNetwork(_req, `/api/account/v1/get_csrf`); const url = buildUrlNode('csrf');
const fetch = fetchFactory(_req); const response = await fetchFactory(_req)(url);
const response = await fetch(url);
if (response.status === 200) { if (response.status === 200) {
const token = response.headers.get('x-bs-account-csrf'); const token = response.headers.get('x-bs-account-csrf');
......
...@@ -2,7 +2,8 @@ import _pick from 'lodash/pick'; ...@@ -2,7 +2,8 @@ import _pick from 'lodash/pick';
import _pickBy from 'lodash/pickBy'; import _pickBy from 'lodash/pickBy';
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch'; import appConfig from 'configs/app/config';
import fetchFactory from 'lib/api/nodeFetch';
const handler = async(_req: NextApiRequest, res: NextApiResponse) => { const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
if (!_req.url) { if (!_req.url) {
...@@ -10,8 +11,12 @@ const handler = async(_req: NextApiRequest, res: NextApiResponse) => { ...@@ -10,8 +11,12 @@ const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
return; return;
} }
const response = await fetchFactory(_req, _req.headers['x-endpoint']?.toString())( const url = new URL(
_req.url.replace(/^\/node-api\/proxy/, ''), _req.url.replace(/^\/node-api\/proxy/, ''),
_req.headers['x-endpoint']?.toString() || appConfig.api.endpoint,
);
const response = await fetchFactory(_req)(
url.toString(),
_pickBy(_pick(_req, [ 'body', 'method' ]), Boolean), _pickBy(_pick(_req, [ 'body', 'method' ]), Boolean),
); );
......
...@@ -19,7 +19,7 @@ const baseStylePopper = defineStyle({ ...@@ -19,7 +19,7 @@ const baseStylePopper = defineStyle({
const baseStyleContent = defineStyle((props) => { const baseStyleContent = defineStyle((props) => {
const bg = mode('white', 'gray.900')(props); const bg = mode('white', 'gray.900')(props);
const shadowColor = mode('gray.200', 'whiteAlpha.300')(props); const shadowColor = mode('blackAlpha.200', 'whiteAlpha.300')(props);
return { return {
[$popperBg.variable]: `colors.${ bg }`, [$popperBg.variable]: `colors.${ bg }`,
......
...@@ -15,7 +15,7 @@ const variantSimple = definePartsStyle((props) => { ...@@ -15,7 +15,7 @@ const variantSimple = definePartsStyle((props) => {
return { return {
th: { th: {
border: 0, border: 0,
color: mode('gray.600', 'whiteAlpha.700')(props), color: mode('blackAlpha.700', 'whiteAlpha.700')(props),
backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props), backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props),
...transitionProps, ...transitionProps,
}, },
......
...@@ -24,7 +24,7 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) { ...@@ -24,7 +24,7 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
}, },
_disabled: { _disabled: {
opacity: 1, opacity: 1,
backgroundColor: mode('gray.200', 'whiteAlpha.200')(props), backgroundColor: mode('blackAlpha.200', 'whiteAlpha.200')(props),
borderColor: 'transparent', borderColor: 'transparent',
cursor: 'not-allowed', cursor: 'not-allowed',
_hover: { _hover: {
......
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
export interface AddressTag { export interface AddressTag {
address_hash: string; address_hash: string;
address: AddressParam;
name: string; name: string;
id: string; id: string;
} }
...@@ -64,7 +65,7 @@ export interface WatchlistAddress { ...@@ -64,7 +65,7 @@ export interface WatchlistAddress {
notification_settings: NotificationSettings; notification_settings: NotificationSettings;
notification_methods: NotificationMethods; notification_methods: NotificationMethods;
id: string; id: string;
address?: AddressParam; address: AddressParam;
} }
export interface WatchlistTokensResponse { export interface WatchlistTokensResponse {
...@@ -89,10 +90,11 @@ export interface PublicTag { ...@@ -89,10 +90,11 @@ export interface PublicTag {
email: string; email: string;
company: string; company: string;
addresses: Array<string>; addresses: Array<string>;
addresses_with_info: Array<AddressParam>;
additional_comment: string; additional_comment: string;
} }
export type PublicTagNew = Omit<PublicTag, 'id'> export type PublicTagNew = Omit<PublicTag, 'id' | 'addresses_with_info'>
export type PublicTags = Array<PublicTag>; export type PublicTags = Array<PublicTag>;
...@@ -102,6 +104,7 @@ export interface CustomAbi { ...@@ -102,6 +104,7 @@ export interface CustomAbi {
name: string; name: string;
id: number; id: number;
contract_address_hash: string; contract_address_hash: string;
contract_address: AddressParam;
abi: Array<AbiItem>; abi: Array<AbiItem>;
} }
......
...@@ -21,11 +21,18 @@ export type GasPrices = { ...@@ -21,11 +21,18 @@ export type GasPrices = {
export type Stats = { export type Stats = {
counters: { counters: {
totalBlocks: string;
averageBlockTime: string; averageBlockTime: string;
totalTransactions: string;
completedTransactions: string; completedTransactions: string;
totalAccounts: string; totalAccounts: string;
totalBlocksAllTime: string;
totalTransactions: string; totalTokens: string;
totalNativeCoinHolders: string;
totalNativeCoinTransfers: string;
}; };
} }
......
...@@ -62,7 +62,7 @@ const AddressDetails = ({ addressQuery }: Props) => { ...@@ -62,7 +62,7 @@ const AddressDetails = ({ addressQuery }: Props) => {
return ( return (
<Box> <Box>
<Flex alignItems="center"> <Flex alignItems="center">
<AddressIcon hash={ addressQuery.data.hash }/> <AddressIcon address={ addressQuery.data }/>
<Text ml={ 2 } fontFamily="heading" fontWeight={ 500 }> <Text ml={ 2 } fontFamily="heading" fontWeight={ 500 }>
{ isMobile ? <HashStringShorten hash={ addressQuery.data.hash }/> : addressQuery.data.hash } { isMobile ? <HashStringShorten hash={ addressQuery.data.hash }/> : addressQuery.data.hash }
</Text> </Text>
......
...@@ -53,7 +53,7 @@ const TxInternalsListItem = ({ ...@@ -53,7 +53,7 @@ const TxInternalsListItem = ({
</HStack> </HStack>
<Box w="100%" display="flex" columnGap={ 3 }> <Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ from.hash }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address> </Address>
{ (isIn || isOut) ? { (isIn || isOut) ?
...@@ -61,7 +61,7 @@ const TxInternalsListItem = ({ ...@@ -61,7 +61,7 @@ const TxInternalsListItem = ({
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/> <Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
} }
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ toData.hash }/> <AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/> <AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/>
</Address> </Address>
</Box> </Box>
......
...@@ -61,7 +61,7 @@ const AddressIntTxsTableItem = ({ ...@@ -61,7 +61,7 @@ const AddressIntTxsTableItem = ({
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon hash={ from.hash }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address> </Address>
</Td> </Td>
...@@ -73,7 +73,7 @@ const AddressIntTxsTableItem = ({ ...@@ -73,7 +73,7 @@ const AddressIntTxsTableItem = ({
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon hash={ toData.hash }/> <AddressIcon address={ toData }/>
<AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/> <AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/>
</Address> </Address>
</Td> </Td>
......
...@@ -57,7 +57,7 @@ const BlockDetails = () => { ...@@ -57,7 +57,7 @@ const BlockDetails = () => {
} }
if (isError) { if (isError) {
const is404 = error?.error?.status === 404; const is404 = error?.payload?.status === 404;
return is404 ? <span>This block has not been processed yet.</span> : <DataFetchAlert/>; return is404 ? <span>This block has not been processed yet.</span> : <DataFetchAlert/>;
} }
......
...@@ -24,7 +24,7 @@ const CustomAbiListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -24,7 +24,7 @@ const CustomAbiListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return ( return (
<ListItemMobile> <ListItemMobile>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name } isContract/> <AddressSnippet address={ item.contract_address } subtitle={ item.name }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</ListItemMobile> </ListItemMobile>
); );
......
...@@ -28,7 +28,7 @@ const CustomAbiTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -28,7 +28,7 @@ const CustomAbiTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return ( return (
<Tr alignItems="top" key={ item.id }> <Tr alignItems="top" key={ item.id }>
<Td> <Td>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name } isContract/> <AddressSnippet address={ item.contract_address } subtitle={ item.name }/>
</Td> </Td>
<Td> <Td>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/> <TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
......
...@@ -36,7 +36,7 @@ const LatestBlocksItem = ({ block, h }: Props) => { ...@@ -36,7 +36,7 @@ const LatestBlocksItem = ({ block, h }: Props) => {
transitionTimingFunction="linear" transitionTimingFunction="linear"
borderRadius="12px" borderRadius="12px"
border="1px solid" border="1px solid"
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') } borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
p={ 6 } p={ 6 }
h={ `${ h }px` } h={ `${ h }px` }
minWidth={{ base: '100%', lg: '280px' }} minWidth={{ base: '100%', lg: '280px' }}
......
...@@ -15,7 +15,7 @@ const LatestBlocksItemSkeleton = () => { ...@@ -15,7 +15,7 @@ const LatestBlocksItemSkeleton = () => {
minWidth={{ base: '100%', lg: '280px' }} minWidth={{ base: '100%', lg: '280px' }}
borderRadius="12px" borderRadius="12px"
border="1px solid" border="1px solid"
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') } borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
p={ 6 } p={ 6 }
> >
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
......
...@@ -37,7 +37,7 @@ type Props = { ...@@ -37,7 +37,7 @@ type Props = {
} }
const LatestBlocksItem = ({ tx }: Props) => { const LatestBlocksItem = ({ tx }: Props) => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const iconColor = useColorModeValue('blue.600', 'blue.300'); const iconColor = useColorModeValue('blue.600', 'blue.300');
const dataTo = tx.to ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
...@@ -109,7 +109,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -109,7 +109,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Box width={{ base: '100%', lg: '50%' }}> <Box width={{ base: '100%', lg: '50%' }}>
<Flex alignItems="center" mb={ 3 } justifyContent={{ base: 'start', lg: 'end' }}> <Flex alignItems="center" mb={ 3 } justifyContent={{ base: 'start', lg: 'end' }}>
<Address> <Address>
<AddressIcon hash={ tx.from.hash }/> <AddressIcon address={ tx.from }/>
<AddressLink <AddressLink
hash={ tx.from.hash } hash={ tx.from.hash }
alias={ tx.from.name } alias={ tx.from.name }
...@@ -126,7 +126,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -126,7 +126,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
color="gray.500" color="gray.500"
/> />
<Address> <Address>
<AddressIcon hash={ dataTo.hash }/> <AddressIcon address={ dataTo }/>
<AddressLink <AddressLink
hash={ dataTo.hash } hash={ dataTo.hash }
alias={ dataTo.name } alias={ dataTo.name }
......
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
import React from 'react'; import React from 'react';
const LatestTxsItemSkeleton = () => { const LatestTxsItemSkeleton = () => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Box <Box
......
...@@ -25,7 +25,7 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -25,7 +25,7 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return ( return (
<ListItemMobile> <ListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%"> <Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<AddressSnippet address={ item.address_hash }/> <AddressSnippet address={ item.address }/>
<HStack spacing={ 3 } mt={ 4 }> <HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text> <Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag> <Tag>
......
...@@ -29,7 +29,7 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -29,7 +29,7 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return ( return (
<Tr alignItems="top" key={ item.id }> <Tr alignItems="top" key={ item.id }>
<Td> <Td>
<AddressSnippet address={ item.address_hash }/> <AddressSnippet address={ item.address }/>
</Td> </Td>
<Td whiteSpace="nowrap"> <Td whiteSpace="nowrap">
<TruncatedTextTooltip label={ item.name }> <TruncatedTextTooltip label={ item.name }>
......
...@@ -27,7 +27,7 @@ const PublicTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -27,7 +27,7 @@ const PublicTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<ListItemMobile> <ListItemMobile>
<VStack spacing={ 3 } alignItems="flex-start" maxW="100%"> <VStack spacing={ 3 } alignItems="flex-start" maxW="100%">
<VStack spacing={ 4 } alignItems="unset" maxW="100%"> <VStack spacing={ 4 } alignItems="unset" maxW="100%">
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) } { item.addresses_with_info.map((address) => <AddressSnippet key={ address.hash } address={ address }/>) }
</VStack> </VStack>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Public tags</Text> <Text fontSize="sm" fontWeight={ 500 }>Public tags</Text>
......
...@@ -32,7 +32,7 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => { ...@@ -32,7 +32,7 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Tr alignItems="top" key={ item.id }> <Tr alignItems="top" key={ item.id }>
<Td> <Td>
<VStack spacing={ 4 } alignItems="unset"> <VStack spacing={ 4 } alignItems="unset">
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) } { item.addresses_with_info.map((address) => <AddressSnippet key={ address.hash } address={ address }/>) }
</VStack> </VStack>
</Td> </Td>
<Td> <Td>
......
import { Text, Box } from '@chakra-ui/react'; import { Text, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AddressParam } from 'types/api/addressParams';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressContractIcon from './address/AddressContractIcon';
interface Props { interface Props {
address: string; address: AddressParam;
subtitle?: string; subtitle?: string;
// temporary solution for custom abis while we don't have address info on account pages
isContract?: boolean;
} }
const AddressSnippet = ({ address, isContract, subtitle }: Props) => { const AddressSnippet = ({ address, subtitle }: Props) => {
return ( return (
<Box maxW="100%"> <Box maxW="100%">
<Address> <Address>
{ isContract ? <AddressContractIcon/> : <AddressIcon hash={ address }/> } <AddressIcon address={ address }/>
<AddressLink hash={ address } fontWeight="600" ml={ 2 }/> <AddressLink hash={ address.hash } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address } ml={ 1 }/> <CopyToClipboard text={ address.hash } ml={ 1 }/>
</Address> </Address>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> } { subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> }
</Box> </Box>
......
...@@ -2,6 +2,9 @@ import { Flex } from '@chakra-ui/react'; ...@@ -2,6 +2,9 @@ import { Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config';
import buildUrl from 'lib/api/buildUrl';
import { getResourceKey } from 'lib/api/useApiQuery';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import AppError from 'ui/shared/AppError/AppError'; import AppError from 'ui/shared/AppError/AppError';
...@@ -23,9 +26,26 @@ const Page = ({ ...@@ -23,9 +26,26 @@ const Page = ({
isHomePage, isHomePage,
renderHeader, renderHeader,
}: Props) => { }: Props) => {
const fetch = useFetch(); const nodeApiFetch = useFetch();
useQuery([ 'csrf' ], async() => await fetch('/node-api/csrf'), { useQuery(getResourceKey('csrf'), async() => {
if (appConfig.host === appConfig.api.host) {
const url = buildUrl('csrf');
const apiResponse = await fetch(url, { credentials: 'include' });
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
// eslint-disable-next-line no-console
console.log('>>> RESPONSE HEADERS <<<');
// eslint-disable-next-line no-console
console.table([ {
'content-length': apiResponse.headers.get('content-length'),
'x-bs-account-csrf': csrfFromHeader,
} ]);
return csrfFromHeader ? { token: csrfFromHeader } : undefined;
}
return nodeApiFetch('/node-api/csrf');
}, {
enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)), enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
}); });
......
...@@ -80,7 +80,7 @@ const TokenTransferListItem = ({ ...@@ -80,7 +80,7 @@ const TokenTransferListItem = ({
) } ) }
<Flex w="100%" columnGap={ 3 }> <Flex w="100%" columnGap={ 3 }>
<Address width={ addressWidth }> <Address width={ addressWidth }>
<AddressIcon hash={ from.hash }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address> </Address>
{ baseAddress ? { baseAddress ?
...@@ -88,7 +88,7 @@ const TokenTransferListItem = ({ ...@@ -88,7 +88,7 @@ const TokenTransferListItem = ({
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/> <Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
} }
<Address width={ addressWidth }> <Address width={ addressWidth }>
<AddressIcon hash={ to.hash }/> <AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash }/> <AddressLink ml={ 2 } fontWeight="500" hash={ to.hash }/>
</Address> </Address>
</Flex> </Flex>
......
...@@ -69,7 +69,7 @@ const TokenTransferTableItem = ({ ...@@ -69,7 +69,7 @@ const TokenTransferTableItem = ({
) } ) }
<Td> <Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon hash={ from.hash }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address> </Address>
</Td> </Td>
...@@ -80,7 +80,7 @@ const TokenTransferTableItem = ({ ...@@ -80,7 +80,7 @@ const TokenTransferTableItem = ({
) } ) }
<Td> <Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px"> <Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon hash={ to.hash }/> <AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 }/> <AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 }/>
</Address> </Address>
</Td> </Td>
......
import { Box, chakra, Tooltip } from '@chakra-ui/react'; import { Box, chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
type Props = { type Props = {
...@@ -6,6 +6,9 @@ type Props = { ...@@ -6,6 +6,9 @@ type Props = {
} }
const AddressContractIcon = ({ className }: Props) => { const AddressContractIcon = ({ className }: Props) => {
const bgColor = useColorModeValue('gray.200', 'gray.600');
const color = useColorModeValue('gray.400', 'gray.200');
return ( return (
<Tooltip label="Contract"> <Tooltip label="Contract">
<Box <Box
...@@ -13,8 +16,8 @@ const AddressContractIcon = ({ className }: Props) => { ...@@ -13,8 +16,8 @@ const AddressContractIcon = ({ className }: Props) => {
width="24px" width="24px"
height="24px" height="24px"
borderRadius="12px" borderRadius="12px"
backgroundColor="gray.200" backgroundColor={ bgColor }
color="gray.400" color={ color }
display="inline-flex" display="inline-flex"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
......
...@@ -2,10 +2,25 @@ import { Box, chakra } from '@chakra-ui/react'; ...@@ -2,10 +2,25 @@ import { Box, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon'; import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
const AddressIcon = ({ hash, className }: {hash: string; className?: string}) => { import type { AddressParam } from 'types/api/addressParams';
import AddressContractIcon from 'ui/shared/address/AddressContractIcon';
type Props = {
address: AddressParam;
className?: string;
}
const AddressIcon = ({ address, className }: Props) => {
if (address.is_contract) {
return (
<AddressContractIcon/>
);
}
return ( return (
<Box className={ className } width="24px" display="inline-flex"> <Box className={ className } width="24px" display="inline-flex">
<Jazzicon diameter={ 24 } seed={ jsNumberForAddress(hash) }/> <Jazzicon diameter={ 24 } seed={ jsNumberForAddress(address.hash) }/>
</Box> </Box>
); );
}; };
......
...@@ -31,6 +31,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -31,6 +31,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
const [ isFullscreen, setIsFullscreen ] = useState(false); const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const pngBackgroundColor = useColorModeValue('white', 'black');
const borderColor = useColorModeValue('gray.200', 'gray.600'); const borderColor = useColorModeValue('gray.200', 'gray.600');
const handleZoom = useCallback(() => { const handleZoom = useCallback(() => {
...@@ -54,11 +55,12 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -54,11 +55,12 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
domToImage.toPng(ref.current, domToImage.toPng(ref.current,
{ {
quality: 100, quality: 100,
bgcolor: 'white', bgcolor: pngBackgroundColor,
width: ref.current.offsetWidth * DOWNLOAD_IMAGE_SCALE, width: ref.current.offsetWidth * DOWNLOAD_IMAGE_SCALE,
height: ref.current.offsetHeight * DOWNLOAD_IMAGE_SCALE, height: ref.current.offsetHeight * DOWNLOAD_IMAGE_SCALE,
filter: (node) => node.nodeName !== 'BUTTON', filter: (node) => node.nodeName !== 'BUTTON',
style: { style: {
borderColor: 'transparent',
transform: `scale(${ DOWNLOAD_IMAGE_SCALE })`, transform: `scale(${ DOWNLOAD_IMAGE_SCALE })`,
'transform-origin': 'top left', 'transform-origin': 'top left',
}, },
...@@ -71,7 +73,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop ...@@ -71,7 +73,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
link.remove(); link.remove();
}); });
} }
}, [ title ]); }, [ pngBackgroundColor, title ]);
const handleSVGSavingClick = useCallback(() => { const handleSVGSavingClick = useCallback(() => {
if (items) { if (items) {
......
...@@ -12,7 +12,7 @@ export default function useColors() { ...@@ -12,7 +12,7 @@ export default function useColors() {
active: useColorModeValue('blue.50', 'gray.800'), active: useColorModeValue('blue.50', 'gray.800'),
}, },
border: { border: {
'default': useColorModeValue('gray.200', 'whiteAlpha.200'), 'default': useColorModeValue('blackAlpha.200', 'whiteAlpha.200'),
active: useColorModeValue('blue.50', 'gray.800'), active: useColorModeValue('blue.50', 'gray.800'),
}, },
}; };
......
...@@ -7,7 +7,7 @@ export default function useColors({ hasIcon }: {hasIcon: boolean}) { ...@@ -7,7 +7,7 @@ export default function useColors({ hasIcon }: {hasIcon: boolean}) {
return { return {
text: { text: {
'default': useColorModeValue('gray.600', 'gray.400'), 'default': useColorModeValue('gray.600', 'gray.400'),
active: useColorModeValue('gray.700', 'whiteAlpha.900'), active: useColorModeValue('blackAlpha.900', 'whiteAlpha.900'),
hover: useColorModeValue('blue.600', 'blue.400'), hover: useColorModeValue('blue.600', 'blue.400'),
}, },
icon: { icon: {
...@@ -19,7 +19,7 @@ export default function useColors({ hasIcon }: {hasIcon: boolean}) { ...@@ -19,7 +19,7 @@ export default function useColors({ hasIcon }: {hasIcon: boolean}) {
active: useColorModeValue('blue.50', 'gray.800'), active: useColorModeValue('blue.50', 'gray.800'),
}, },
border: { border: {
'default': useColorModeValue('gray.200', 'whiteAlpha.200'), 'default': useColorModeValue('blackAlpha.200', 'whiteAlpha.200'),
active: useColorModeValue('blue.50', 'gray.800'), active: useColorModeValue('blue.50', 'gray.800'),
}, },
}; };
......
...@@ -12,8 +12,8 @@ type Props = UserInfo; ...@@ -12,8 +12,8 @@ type Props = UserInfo;
const ProfileMenuContent = ({ name, nickname, email }: Props) => { const ProfileMenuContent = ({ name, nickname, email }: Props) => {
const { accountNavItems, profileItem } = useNavItems(); const { accountNavItems, profileItem } = useNavItems();
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const primaryTextColor = useColorModeValue('gray.600', 'whiteAlpha.800'); const primaryTextColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
return ( return (
<Box> <Box>
......
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { numberWidgetsScheme } from './constants/number-widgets-scheme';
import NumberWidget from './NumberWidget'; import NumberWidget from './NumberWidget';
import NumberWidgetSkeleton from './NumberWidgetSkeleton'; import NumberWidgetSkeleton from './NumberWidgetSkeleton';
...@@ -18,30 +19,8 @@ const NumberWidgetsList = () => { ...@@ -18,30 +19,8 @@ const NumberWidgetsList = () => {
> >
{ isLoading ? [ ...Array(skeletonsCount) ] { isLoading ? [ ...Array(skeletonsCount) ]
.map((e, i) => <NumberWidgetSkeleton key={ i }/>) : .map((e, i) => <NumberWidgetSkeleton key={ i }/>) :
( numberWidgetsScheme.map(({ id, title }) =>
<> data?.counters[id] ? <NumberWidget key={ id } label={ title } value={ Number(data.counters[id]).toLocaleString() }/> : null) }
<NumberWidget
label="Total blocks"
value={ Number(data?.counters.totalBlocksAllTime).toLocaleString() }
/>
<NumberWidget
label="Average block time"
value={ Number(data?.counters.averageBlockTime).toLocaleString() }
/>
<NumberWidget
label="Completed transactions"
value={ Number(data?.counters.completedTransactions).toLocaleString() }
/>
<NumberWidget
label="Total transactions"
value={ Number(data?.counters.totalTransactions).toLocaleString() }
/>
<NumberWidget
label="Total accounts"
value={ Number(data?.counters.totalAccounts).toLocaleString() }
/>
</>
) }
</Grid> </Grid>
); );
}; };
......
...@@ -6,57 +6,57 @@ export const statsChartsScheme: Array<StatsSection> = [ ...@@ -6,57 +6,57 @@ export const statsChartsScheme: Array<StatsSection> = [
title: 'Blocks', title: 'Blocks',
charts: [ charts: [
{ {
id: 'new-blocks', id: 'newBlocksPerDay',
title: 'New blocks', title: 'New blocks',
description: 'New blocks number per day', description: 'New blocks number per day',
}, },
{ // {
id: 'average-block-size', // id: 'average-block-size',
title: 'Average block size', // title: 'Average block size',
description: 'Average size of blocks in bytes', // description: 'Average size of blocks in bytes',
}, // },
],
},
{
id: 'transactions',
title: 'Transactions',
charts: [
{
id: 'average-transaction-fee',
title: 'Average transaction fee',
description: 'The average amount in USD spent per transaction',
},
{
id: 'transactions-fees',
title: 'Transactions fees',
description: 'Amount of tokens paid as fees',
},
{
id: 'new-transactions',
title: 'Transactions fees',
description: 'New transactions number per period',
},
{
id: 'transactions-growth',
title: 'Transactions growth',
description: 'Cumulative transactions number per period',
},
],
},
{
id: 'accounts',
title: 'Accounts',
charts: [
{
id: 'active-accounts',
title: 'Active accounts',
description: 'Active accounts number per period',
},
{
id: 'accounts-growth',
title: 'Accounts growth',
description: 'Cumulative accounts number per period',
},
], ],
}, },
// {
// id: 'transactions',
// title: 'Transactions',
// charts: [
// {
// id: 'average-transaction-fee',
// title: 'Average transaction fee',
// description: 'The average amount in USD spent per transaction',
// },
// {
// id: 'transactions-fees',
// title: 'Transactions fees',
// description: 'Amount of tokens paid as fees',
// },
// {
// id: 'new-transactions',
// title: 'Transactions fees',
// description: 'New transactions number per period',
// },
// {
// id: 'transactions-growth',
// title: 'Transactions growth',
// description: 'Cumulative transactions number per period',
// },
// ],
// },
// {
// id: 'accounts',
// title: 'Accounts',
// charts: [
// {
// id: 'active-accounts',
// title: 'Active accounts',
// description: 'Active accounts number per period',
// },
// {
// id: 'accounts-growth',
// title: 'Accounts growth',
// description: 'Cumulative accounts number per period',
// },
// ],
// },
]; ];
import type { Stats } from 'types/api/stats';
type Key = keyof Stats['counters'];
export const numberWidgetsScheme: Array<{id: Key; title: string}> = [
{
id: 'totalBlocks',
title: 'Total blocks',
},
{
id: 'averageBlockTime',
title: 'Average block time',
},
{
id: 'totalTransactions',
title: 'Total transactions',
},
{
id: 'completedTransactions',
title: 'Completed transactions',
},
{
id: 'totalAccounts',
title: 'Total accounts',
},
{
id: 'totalTokens',
title: 'Total tokens',
},
{
id: 'totalNativeCoinHolders',
title: 'Total native coin holders',
},
{
id: 'totalNativeCoinTransfers',
title: 'Total native coin transfers',
},
{
id: 'totalAccounts',
title: 'Total accounts',
},
];
...@@ -27,7 +27,6 @@ test('between addresses +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -27,7 +27,6 @@ test('between addresses +@mobile +@dark-mode', async({ mount, page }) => {
{ hooksConfig }, { hooksConfig },
); );
await page.waitForResponse(API_URL);
await page.getByText('View details').click(); await page.getByText('View details').click();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
...@@ -45,7 +44,6 @@ test('creating contact', async({ mount, page }) => { ...@@ -45,7 +44,6 @@ test('creating contact', async({ mount, page }) => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -62,7 +60,6 @@ test('with token transfer +@mobile', async({ mount, page }) => { ...@@ -62,7 +60,6 @@ test('with token transfer +@mobile', async({ mount, page }) => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -79,7 +76,6 @@ test('with decoded revert reason', async({ mount, page }) => { ...@@ -79,7 +76,6 @@ test('with decoded revert reason', async({ mount, page }) => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -96,7 +92,6 @@ test('with decoded raw reason', async({ mount, page }) => { ...@@ -96,7 +92,6 @@ test('with decoded raw reason', async({ mount, page }) => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
}); });
...@@ -113,7 +108,7 @@ test('pending', async({ mount, page }) => { ...@@ -113,7 +108,7 @@ test('pending', async({ mount, page }) => {
</TestApp>, </TestApp>,
{ hooksConfig }, { hooksConfig },
); );
await page.waitForResponse(API_URL);
await page.getByText('View details').click(); await page.getByText('View details').click();
await expect(component).toHaveScreenshot(); await expect(component).toHaveScreenshot();
......
...@@ -147,7 +147,7 @@ const TxDetails = () => { ...@@ -147,7 +147,7 @@ const TxDetails = () => {
columnGap={ 3 } columnGap={ 3 }
> >
<Address> <Address>
<AddressIcon hash={ data.from.hash }/> <AddressIcon address={ data.from }/>
<AddressLink ml={ 2 } hash={ data.from.hash }/> <AddressLink ml={ 2 } hash={ data.from.hash }/>
<CopyToClipboard text={ data.from.hash }/> <CopyToClipboard text={ data.from.hash }/>
</Address> </Address>
...@@ -166,7 +166,7 @@ const TxDetails = () => { ...@@ -166,7 +166,7 @@ const TxDetails = () => {
> >
{ data.to && data.to.hash ? ( { data.to && data.to.hash ? (
<Address alignItems="center"> <Address alignItems="center">
<AddressIcon hash={ toAddress.hash }/> <AddressIcon address={ toAddress }/>
<AddressLink ml={ 2 } hash={ toAddress.hash }/> <AddressLink ml={ 2 } hash={ toAddress.hash }/>
{ executionSuccessBadge } { executionSuccessBadge }
{ executionFailedBadge } { executionFailedBadge }
......
...@@ -27,12 +27,12 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit: ...@@ -27,12 +27,12 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
</Flex> </Flex>
<Box w="100%" display="flex" columnGap={ 3 }> <Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ from.hash }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address> </Address>
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/> <Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
<Address width="calc((100% - 48px) / 2)"> <Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ toData.hash }/> <AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/> <AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/>
</Address> </Address>
</Box> </Box>
......
...@@ -32,7 +32,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: ...@@ -32,7 +32,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon hash={ from.hash }/> <AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/> <AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address> </Address>
</Td> </Td>
...@@ -41,7 +41,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: ...@@ -41,7 +41,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%"> <Address display="inline-flex" maxW="100%">
<AddressIcon hash={ toData.hash }/> <AddressIcon address={ toData }/>
<AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/> <AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/>
</Address> </Address>
</Td> </Td>
......
...@@ -50,7 +50,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => { ...@@ -50,7 +50,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
<RowHeader>Address</RowHeader> <RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center"> <GridItem display="flex" alignItems="center">
<Address mr={{ base: 9, lg: 0 }}> <Address mr={{ base: 9, lg: 0 }}>
<AddressIcon hash={ address.hash }/> <AddressIcon address={ address }/>
<AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/> <AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address> </Address>
{ /* api doesn't have find topic feature yet */ } { /* api doesn't have find topic feature yet */ }
......
...@@ -9,7 +9,7 @@ import type { data } from 'data/txState'; ...@@ -9,7 +9,7 @@ import type { data } from 'data/txState';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle'; import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; // import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
...@@ -51,7 +51,8 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props ...@@ -51,7 +51,8 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
<AccordionIcon color="blue.600" width="30px"/> <AccordionIcon color="blue.600" width="30px"/>
</AccordionButton> </AccordionButton>
<Address flexGrow={ 1 }> <Address flexGrow={ 1 }>
<AddressIcon hash={ address }/> { /* ??? */ }
{ /* <AddressIcon hash={ address }/> */ }
<AddressLink hash={ address } ml={ 2 }/> <AddressLink hash={ address } ml={ 2 }/>
</Address> </Address>
</Flex> </Flex>
......
...@@ -18,7 +18,7 @@ import React, { useRef } from 'react'; ...@@ -18,7 +18,7 @@ import React, { useRef } from 'react';
import type { TTxStateItem } from 'data/txState'; import type { TTxStateItem } from 'data/txState';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; // import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import TxStateStorageItem from './TxStateStorageItem'; import TxStateStorageItem from './TxStateStorageItem';
...@@ -58,7 +58,8 @@ const TxStateTableItem = ({ txStateItem }: { txStateItem: TTxStateItem }) => { ...@@ -58,7 +58,8 @@ const TxStateTableItem = ({ txStateItem }: { txStateItem: TTxStateItem }) => {
</Td> </Td>
<Td border={ 0 }> <Td border={ 0 }>
<Address height="30px"> <Address height="30px">
<AddressIcon hash={ txStateItem.address }/> { /* ??? */ }
{ /* <AddressIcon hash={ txStateItem.address }/> */ }
<AddressLink hash={ txStateItem.address } fontWeight="500" truncation="constant" ml={ 2 }/> <AddressLink hash={ txStateItem.address } fontWeight="500" truncation="constant" ml={ 2 }/>
</Address> </Address>
</Td> </Td>
......
...@@ -12,7 +12,7 @@ import TextSeparator from 'ui/shared/TextSeparator'; ...@@ -12,7 +12,7 @@ import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
const TxAdditionalInfo = ({ tx }: { tx: Transaction }) => { const TxAdditionalInfo = ({ tx }: { tx: Transaction }) => {
const sectionBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const sectionBorderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const sectionProps = { const sectionProps = {
borderBottom: '1px solid', borderBottom: '1px solid',
borderColor: sectionBorderColor, borderColor: sectionBorderColor,
......
...@@ -72,7 +72,7 @@ const TxsFilters = ({ onFiltersChange, filters, appliedFiltersNum }: Props) => { ...@@ -72,7 +72,7 @@ const TxsFilters = ({ onFiltersChange, filters, appliedFiltersNum }: Props) => {
onClose(); onClose();
}, [ onClose, onFiltersChange, typeFilter, methodFilter ]); }, [ onClose, onFiltersChange, typeFilter, methodFilter ]);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy> <Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
......
...@@ -102,7 +102,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }: ...@@ -102,7 +102,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
) } ) }
<Flex alignItems="center" height={ 6 } mt={ 6 }> <Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address width={ `calc((100%-${ currentAddress ? TAG_WIDTH : ARROW_WIDTH + 8 }px)/2)` }> <Address width={ `calc((100%-${ currentAddress ? TAG_WIDTH : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon hash={ tx.from.hash }/> <AddressIcon address={ tx.from }/>
<AddressLink <AddressLink
hash={ tx.from.hash } hash={ tx.from.hash }
alias={ tx.from.name } alias={ tx.from.name }
...@@ -120,7 +120,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }: ...@@ -120,7 +120,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
/> />
) } ) }
<Address width="calc((100%-40px)/2)"> <Address width="calc((100%-40px)/2)">
<AddressIcon hash={ dataTo.hash }/> <AddressIcon address={ dataTo }/>
<AddressLink <AddressLink
hash={ dataTo.hash } hash={ dataTo.hash }
alias={ dataTo.name } alias={ dataTo.name }
......
...@@ -51,7 +51,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement } ...@@ -51,7 +51,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressFrom = ( const addressFrom = (
<Address> <Address>
<Tooltip label={ tx.from.implementation_name }> <Tooltip label={ tx.from.implementation_name }>
<Box display="flex"><AddressIcon hash={ tx.from.hash }/></Box> <Box display="flex"><AddressIcon address={ tx.from }/></Box>
</Tooltip> </Tooltip>
<AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant"/> <AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant"/>
</Address> </Address>
...@@ -62,13 +62,13 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement } ...@@ -62,13 +62,13 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressTo = ( const addressTo = (
<Address> <Address>
<Tooltip label={ dataTo.implementation_name }> <Tooltip label={ dataTo.implementation_name }>
<Box display="flex"><AddressIcon hash={ dataTo.hash }/></Box> <Box display="flex"><AddressIcon address={ dataTo }/></Box>
</Tooltip> </Tooltip>
<AddressLink hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant"/> <AddressLink hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant"/>
</Address> </Address>
); );
const infoBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const infoBorderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<Tr> <Tr>
<Td pl={ 4 }> <Td pl={ 4 }>
......
...@@ -16,7 +16,7 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => { ...@@ -16,7 +16,7 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
return ( return (
<VStack spacing={ 2 } align="stretch" fontWeight={ 500 }> <VStack spacing={ 2 } align="stretch" fontWeight={ 500 }>
<AddressSnippet address={ item.address_hash }/> <AddressSnippet address={ item.address }/>
<Flex fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft } flexWrap="wrap" alignItems="center" rowGap={ 1 }> <Flex fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft } flexWrap="wrap" alignItems="center" rowGap={ 1 }>
{ appConfig.network.currency.address && ( { appConfig.network.currency.address && (
<TokenLogo <TokenLogo
......
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