Commit f8d90439 authored by tom's avatar tom

Merge branch 'api-changes' into pw-tests

parents 9a595730 e0954498
...@@ -60,9 +60,9 @@ const config = Object.freeze({ ...@@ -60,9 +60,9 @@ const config = Object.freeze({
name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_NAME), name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_NAME),
symbol: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL), symbol: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL),
decimals: Number(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS)) || DEFAULT_CURRENCY_DECIMALS, decimals: Number(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS)) || DEFAULT_CURRENCY_DECIMALS,
address: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS),
}, },
assetsPathname: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME), assetsPathname: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME),
nativeTokenAddress: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS),
explorers: parseEnvJson<Array<NetworkExplorer>>(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_EXPLORERS)) || [], explorers: parseEnvJson<Array<NetworkExplorer>>(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_EXPLORERS)) || [],
verificationType: process.env.NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE || 'mining', verificationType: process.env.NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE || 'mining',
}, },
......
...@@ -31,8 +31,9 @@ blockscout: ...@@ -31,8 +31,9 @@ blockscout:
tls: tls:
enabled: true enabled: true
path: path:
# - "/poa/sokol(/|$)(.*)" prefix:
- "/" # - "/poa/sokol(/|$)(.*)"
- "/"
# probes # probes
livenessProbe: livenessProbe:
enabled: true enabled: true
...@@ -43,14 +44,14 @@ blockscout: ...@@ -43,14 +44,14 @@ blockscout:
resources: resources:
limits: limits:
memory: memory:
_default: "1Gi" _default: "4Gi"
cpu: cpu:
_default: "2" _default: "4"
requests: requests:
memory: memory:
_default: "1Gi" _default: "4Gi"
cpu: cpu:
_default: "2" _default: "4"
# enable service to connect to RDS # enable service to connect to RDS
rds: rds:
enabled: false enabled: false
...@@ -152,17 +153,19 @@ postgres: ...@@ -152,17 +153,19 @@ postgres:
command: '["docker-entrypoint.sh", "-c"]' command: '["docker-entrypoint.sh", "-c"]'
args: '["max_connections=300"]' args: '["max_connections=300"]'
strategy: Recreate
resources: resources:
limits: limits:
memory: memory:
_default: "1Gi" _default: "2Gi"
cpu: cpu:
_default: "1" _default: "2"
requests: requests:
memory: memory:
_default: "1Gi" _default: "2Gi"
cpu: cpu:
_default: "1" _default: "2"
environment: environment:
POSTGRES_USER: POSTGRES_USER:
...@@ -434,18 +437,20 @@ frontend: ...@@ -434,18 +437,20 @@ frontend:
tls: tls:
enabled: true enabled: true
path: path:
# - "/(apps|auth/profile|account)" exact:
- "/" # - "/(apps|auth/profile|account)"
- "/apps" - "/"
- "/_next" prefix:
- "/node-api" - "/apps"
- "/static" - "/_next"
- "/auth/profile" - "/node-api"
- "/account" - "/static"
- "/txs" - "/auth/profile"
- "/tx" - "/account"
- "/blocks" - "/txs"
- "/block" - "/tx"
- "/blocks"
- "/block"
resources: resources:
limits: limits:
memory: memory:
...@@ -504,7 +509,7 @@ frontend: ...@@ -504,7 +509,7 @@ frontend:
NEXT_PUBLIC_FOOTER_TWITTER_LINK: NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom _default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV: NEXT_PUBLIC_APP_ENV:
_default: preview _default: stable
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
_default: unknown _default: unknown
NEXT_PUBLIC_API_HOST: NEXT_PUBLIC_API_HOST:
......
...@@ -31,8 +31,9 @@ blockscout: ...@@ -31,8 +31,9 @@ blockscout:
tls: tls:
enabled: true enabled: true
path: path:
prefix:
# - "/poa/sokol(/|$)(.*)" # - "/poa/sokol(/|$)(.*)"
- "/" - "/"
# probes # probes
livenessProbe: livenessProbe:
enabled: true enabled: true
...@@ -123,6 +124,8 @@ postgres: ...@@ -123,6 +124,8 @@ postgres:
command: '["docker-entrypoint.sh", "-c"]' command: '["docker-entrypoint.sh", "-c"]'
args: '["max_connections=300"]' args: '["max_connections=300"]'
strategy: Recreate
resources: resources:
limits: limits:
memory: memory:
...@@ -299,17 +302,20 @@ frontend: ...@@ -299,17 +302,20 @@ frontend:
tls: tls:
enabled: true enabled: true
path: path:
exact:
# - "/(apps|auth/profile|account)"
- "/"
prefix:
# - "/(apps|auth/profile|account)" # - "/(apps|auth/profile|account)"
- "/" - "/apps"
- "/apps" - "/_next"
- "/_next" - "/node-api"
- "/node-api" - "/static"
- "/static" - "/auth/profile"
- "/auth/profile" - "/txs"
- "/txs" - "/tx"
- "/tx" - "/blocks"
- "/blocks" - "/block"
- "/block"
resources: resources:
limits: limits:
memory: memory:
......
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m6 5.293 2.475-2.475.707.707L6.707 6l2.475 2.475-.707.707L6 6.707 3.525 9.182l-.707-.707L5.293 6 2.818 3.525l.707-.707L6 5.293Z" fill="currentColor"/>
</svg>
...@@ -5,10 +5,8 @@ import { useRouter } from 'next/router'; ...@@ -5,10 +5,8 @@ import { useRouter } from 'next/router';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll } from 'react-scroll';
import type { BlockFilters } from 'types/api/block';
import { PAGINATION_FIELDS } from 'types/api/pagination'; import { PAGINATION_FIELDS } from 'types/api/pagination';
import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys } from 'types/api/pagination'; import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys, PaginationFilters } from 'types/api/pagination';
import type { TTxsFilters } from 'types/api/txsFilters';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
...@@ -16,7 +14,7 @@ interface Params<QueryName extends PaginatedQueryKeys> { ...@@ -16,7 +14,7 @@ interface Params<QueryName extends PaginatedQueryKeys> {
apiPath: string; apiPath: string;
queryName: QueryName; queryName: QueryName;
queryIds?: Array<string>; queryIds?: Array<string>;
filters?: TTxsFilters | BlockFilters; filters?: PaginationFilters<QueryName>;
options?: Omit<UseQueryOptions<unknown, unknown, PaginatedResponse<QueryName>>, 'queryKey' | 'queryFn'>; options?: Omit<UseQueryOptions<unknown, unknown, PaginatedResponse<QueryName>>, 'queryKey' | 'queryFn'>;
} }
......
import handler from 'lib/api/handler';
const getUrl = () => '/v2/stats';
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
// todo_tom leave only one api endpoint
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const getUrl = () => '/v2/stats'; const getUrl = () => '/v2/stats';
......
...@@ -2,14 +2,21 @@ import type { AddressParam } from './addressParams'; ...@@ -2,14 +2,21 @@ import type { AddressParam } from './addressParams';
export type TxInternalsType = 'call' | 'delegatecall' | 'staticcall' | 'create' | 'create2' | 'selfdestruct' | 'reward' export type TxInternalsType = 'call' | 'delegatecall' | 'staticcall' | 'create' | 'create2' | 'selfdestruct' | 'reward'
export interface InternalTransaction { export type InternalTransaction = (
{
to: AddressParam;
created_contract: null;
} |
{
to: null;
created_contract: AddressParam;
}
) & {
error: string | null; error: string | null;
success: boolean; success: boolean;
type: TxInternalsType; type: TxInternalsType;
transaction_hash: string; transaction_hash: string;
from: AddressParam; from: AddressParam;
to: AddressParam;
created_contract: AddressParam;
value: string; value: string;
index: number; index: number;
block: number; block: number;
......
import type { BlocksResponse, BlockTransactionsResponse } from 'types/api/block'; import type { BlocksResponse, BlockTransactionsResponse, BlockFilters } from 'types/api/block';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponse } from 'types/api/log'; import type { LogsResponse } from 'types/api/log';
import type { TokenTransferResponse } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending } from 'types/api/transaction'; import type { TransactionsResponseValidated, TransactionsResponsePending } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
import { QueryKeys } from 'types/client/queries'; import { QueryKeys } from 'types/client/queries';
import type { KeysOfObjectOrNull } from 'types/utils/KeysOfObjectOrNull'; import type { KeysOfObjectOrNull } from 'types/utils/KeysOfObjectOrNull';
...@@ -25,6 +26,13 @@ export type PaginatedResponse<Q extends PaginatedQueryKeys> = ...@@ -25,6 +26,13 @@ export type PaginatedResponse<Q extends PaginatedQueryKeys> =
Q extends QueryKeys.txTokenTransfers ? TokenTransferResponse : Q extends QueryKeys.txTokenTransfers ? TokenTransferResponse :
never never
export type PaginationFilters<Q extends PaginatedQueryKeys> =
Q extends QueryKeys.blocks ? BlockFilters :
Q extends QueryKeys.txsValidate ? TTxsFilters :
Q extends QueryKeys.txsPending ? TTxsFilters :
Q extends QueryKeys.txTokenTransfers ? TokenTransferFilters :
never
export type PaginationParams<Q extends PaginatedQueryKeys> = PaginatedResponse<Q>['next_page_params']; export type PaginationParams<Q extends PaginatedQueryKeys> = PaginatedResponse<Q>['next_page_params'];
type PaginationFields = { type PaginationFields = {
......
...@@ -10,4 +10,5 @@ export type Stats = { ...@@ -10,4 +10,5 @@ export type Stats = {
gas_prices: {average: number; fast: number; slow: number}; gas_prices: {average: number; fast: number; slow: number};
static_gas_price: string; static_gas_price: string;
market_cap: string; market_cap: string;
network_utilization_percentage: number;
} }
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { TokenInfoGeneric } from './tokenInfo'; import type { TokenInfoGeneric, TokenType } from './tokenInfo';
export type Erc20TotalPayload = { export type Erc20TotalPayload = {
decimals: string | null; decimals: string | null;
...@@ -47,3 +47,7 @@ export interface TokenTransferResponse { ...@@ -47,3 +47,7 @@ export interface TokenTransferResponse {
transaction_hash: string; transaction_hash: string;
} | null; } | null;
} }
export interface TokenTransferFilters {
type: Array<TokenType>;
}
...@@ -5,10 +5,18 @@ import type { TokenTransfer } from './tokenTransfer'; ...@@ -5,10 +5,18 @@ import type { TokenTransfer } from './tokenTransfer';
export type TransactionRevertReason = { export type TransactionRevertReason = {
raw: string; raw: string;
decoded: string;
} | DecodedInput; } | DecodedInput;
export interface Transaction { export type Transaction = (
{
to: AddressParam;
created_contract: null;
} |
{
to: null;
created_contract: AddressParam;
}
) & {
hash: string; hash: string;
result: string; result: string;
confirmations: number; confirmations: number;
...@@ -17,8 +25,6 @@ export interface Transaction { ...@@ -17,8 +25,6 @@ export interface Transaction {
timestamp: string | null; timestamp: string | null;
confirmation_duration: Array<number>; confirmation_duration: Array<number>;
from: AddressParam; from: AddressParam;
to: AddressParam | null;
created_contract: AddressParam;
value: string; value: string;
fee: Fee; fee: Fee;
gas_price: string; gas_price: string;
......
...@@ -120,7 +120,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -120,7 +120,7 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
return ( return (
<> <form noValidate onSubmit={ handleSubmit(onSubmit) }>
{ data && ( { data && (
<Box marginBottom={ 5 }> <Box marginBottom={ 5 }>
<Controller <Controller
...@@ -144,14 +144,14 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -144,14 +144,14 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Button
size="lg" size="lg"
onClick={ handleSubmit(onSubmit) } type="submit"
disabled={ !isValid } disabled={ !isValid }
isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
{ data ? 'Save' : 'Generate API key' } { data ? 'Save' : 'Generate API key' }
</Button> </Button>
</Box> </Box>
</> </form>
); );
}; };
......
import { import {
Box, Button, Heading, Icon, IconButton, Image, Link, List, Modal, ModalBody, Box, Button, Flex, Heading, Icon, IconButton, Image, Link, List, Modal, ModalBody,
ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Tag, Text, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Tag, Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import NextLink from 'next/link'; import NextLink from 'next/link';
...@@ -79,7 +79,9 @@ const AppModal = ({ ...@@ -79,7 +79,9 @@ const AppModal = ({
gridTemplateColumns={{ base: 'auto 1fr' }} gridTemplateColumns={{ base: 'auto 1fr' }}
paddingRight={{ sm: 12 }} paddingRight={{ sm: 12 }}
> >
<Box <Flex
alignItems="center"
justifyContent="center"
w={{ base: '72px', sm: '144px' }} w={{ base: '72px', sm: '144px' }}
h={{ base: '72px', sm: '144px' }} h={{ base: '72px', sm: '144px' }}
marginRight={{ base: 6, sm: 8 }} marginRight={{ base: 6, sm: 8 }}
...@@ -89,7 +91,7 @@ const AppModal = ({ ...@@ -89,7 +91,7 @@ const AppModal = ({
src={ logo } src={ logo }
alt={ `${ title } app icon` } alt={ `${ title } app icon` }
/> />
</Box> </Flex>
<Heading <Heading
as="h2" as="h2"
......
...@@ -104,7 +104,7 @@ const BlocksContent = ({ type }: Props) => { ...@@ -104,7 +104,7 @@ const BlocksContent = ({ type }: Props) => {
return ( return (
<> <>
{ data ? { !isLoading ?
totalText : totalText :
<Skeleton h="24px" w="200px" mb={{ base: 0, lg: 6 }}/> <Skeleton h="24px" w="200px" mb={{ base: 0, lg: 6 }}/>
} }
......
...@@ -139,7 +139,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -139,7 +139,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
return ( return (
<> <form noValidate onSubmit={ handleSubmit(onSubmit) }>
<Box> <Box>
<Controller <Controller
name="contract_address_hash" name="contract_address_hash"
...@@ -170,14 +170,14 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -170,14 +170,14 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Button
size="lg" size="lg"
onClick={ handleSubmit(onSubmit) } type="submit"
disabled={ !isValid } disabled={ !isValid }
isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
{ data ? 'Save' : 'Create custom ABI' } { data ? 'Save' : 'Create custom ABI' }
</Button> </Button>
</Box> </Box>
</> </form>
); );
}; };
......
...@@ -5,6 +5,7 @@ import React from 'react'; ...@@ -5,6 +5,7 @@ import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { SocketMessage } from 'lib/socket/types';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
import type { Stats } from 'types/api/stats';
import { QueryKeys } from 'types/client/queries'; import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
...@@ -26,10 +27,14 @@ const LatestBlocks = () => { ...@@ -26,10 +27,14 @@ const LatestBlocks = () => {
const fetch = useFetch(); const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, Array<Block>>( const { data, isLoading, isError } = useQuery<unknown, unknown, Array<Block>>(
[ QueryKeys.indexBlocks ], [ QueryKeys.indexBlocks ],
async() => await fetch(`/api/index/blocks`), async() => await fetch(`/node-api/index/blocks`),
); );
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const statsQueryResult = useQuery<unknown, unknown, Stats>(
[ QueryKeys.stats ],
() => fetch('/node-api/stats'),
);
const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => { const handleNewBlockMessage: SocketMessage.NewBlock['handler'] = React.useCallback((payload) => {
queryClient.setQueryData([ QueryKeys.indexBlocks ], (prevData: Array<Block> | undefined) => { queryClient.setQueryData([ QueryKeys.indexBlocks ], (prevData: Array<Block> | undefined) => {
...@@ -78,15 +83,20 @@ const LatestBlocks = () => { ...@@ -78,15 +83,20 @@ const LatestBlocks = () => {
content = ( content = (
<> <>
<Box mb={{ base: 6, lg: 9 }}> { statsQueryResult.isLoading && (
<Text as="span" fontSize="sm"> <Skeleton h="24px" w="170px" mb={{ base: 6, lg: 9 }}/>
Network utilization:{ nbsp } ) }
</Text> { statsQueryResult.data?.network_utilization_percentage && (
{ /* Not implemented in API yet */ } <Box mb={{ base: 6, lg: 9 }}>
<Text as="span" fontSize="sm" color="blue.400" fontWeight={ 700 }> <Text as="span" fontSize="sm">
43.8% Network utilization:{ nbsp }
</Text> </Text>
</Box> { /* Not implemented in API yet */ }
<Text as="span" fontSize="sm" color="blue.400" fontWeight={ 700 }>
{ statsQueryResult.data.network_utilization_percentage.toFixed(2) }%
</Text>
</Box>
) }
<VStack spacing={ `${ BLOCK_MARGIN }px` } mb={ 6 } height={ `${ BLOCK_HEIGHT * blocksCount + BLOCK_MARGIN * (blocksCount - 1) }px` } overflow="hidden"> <VStack spacing={ `${ BLOCK_MARGIN }px` } mb={ 6 } height={ `${ BLOCK_HEIGHT * blocksCount + BLOCK_MARGIN * (blocksCount - 1) }px` } overflow="hidden">
<AnimatePresence initial={ false } > <AnimatePresence initial={ false } >
{ dataToShow.map((block => <LatestBlocksItem key={ block.height } block={ block } h={ BLOCK_HEIGHT }/>)) } { dataToShow.map((block => <LatestBlocksItem key={ block.height } block={ block } h={ BLOCK_HEIGHT }/>)) }
......
...@@ -19,7 +19,7 @@ const LatestTransactions = () => { ...@@ -19,7 +19,7 @@ const LatestTransactions = () => {
const fetch = useFetch(); const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, Array<Transaction>>( const { data, isLoading, isError } = useQuery<unknown, unknown, Array<Transaction>>(
[ QueryKeys.indexTxs ], [ QueryKeys.indexTxs ],
async() => await fetch(`/api/index/txs`), async() => await fetch(`/node-api/index/txs`),
); );
let content; let content;
......
...@@ -40,7 +40,7 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -40,7 +40,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const iconColor = useColorModeValue('blue.600', 'blue.300'); const iconColor = useColorModeValue('blue.600', 'blue.300');
const dataTo = tx.to && tx.to.hash ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
const timeAgo = useTimeAgoIncrement(tx.timestamp || '0', true); const timeAgo = useTimeAgoIncrement(tx.timestamp || '0', true);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -100,7 +100,6 @@ const LatestBlocksItem = ({ tx }: Props) => { ...@@ -100,7 +100,6 @@ const LatestBlocksItem = ({ tx }: Props) => {
type="transaction" type="transaction"
fontWeight="700" fontWeight="700"
truncation="constant" truncation="constant"
target="_self"
/> />
</Address> </Address>
</Flex> </Flex>
......
...@@ -29,7 +29,7 @@ const LatestTxsNotice = ({ className }: Props) => { ...@@ -29,7 +29,7 @@ const LatestTxsNotice = ({ className }: Props) => {
<> <>
<Spinner size="sm" mr={ 3 }/> <Spinner size="sm" mr={ 3 }/>
<Text as="span" whiteSpace="pre">+ { num } new transaction{ num > 1 ? 's' : '' }. </Text> <Text as="span" whiteSpace="pre">+ { num } new transaction{ num > 1 ? 's' : '' }. </Text>
<Link href={ txsUrl }>Show in list</Link> <Link href={ txsUrl }>View all</Link>
</> </>
); );
} }
......
...@@ -28,7 +28,7 @@ const Stats = () => { ...@@ -28,7 +28,7 @@ const Stats = () => {
const { data, isLoading, isError } = useQuery<unknown, unknown, Stats>( const { data, isLoading, isError } = useQuery<unknown, unknown, Stats>(
[ QueryKeys.stats ], [ QueryKeys.stats ],
async() => await fetch(`/api/index/stats`), async() => await fetch(`/node-api/stats`),
); );
if (isError) { if (isError) {
......
...@@ -34,7 +34,7 @@ const coinPriceIndicator: TChainIndicator<QueryKeys.chartsMarket> = { ...@@ -34,7 +34,7 @@ const coinPriceIndicator: TChainIndicator<QueryKeys.chartsMarket> = {
id: 'coin_price', id: 'coin_price',
title: `${ appConfig.network.currency.symbol } price`, title: `${ appConfig.network.currency.symbol } price`,
value: (stats) => '$' + Number(stats.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }), value: (stats) => '$' + Number(stats.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }),
icon: <TokenLogo hash={ appConfig.network.nativeTokenAddress || '' } name={ appConfig.network.currency.name } boxSize={ 6 }/>, icon: <TokenLogo hash={ appConfig.network.currency.address || '' } name={ appConfig.network.currency.name } boxSize={ 6 }/>,
hint: `${ appConfig.network.currency.symbol } token daily price in USD.`, hint: `${ appConfig.network.currency.symbol } token daily price in USD.`,
api: { api: {
queryName: QueryKeys.chartsMarket, queryName: QueryKeys.chartsMarket,
......
...@@ -93,7 +93,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -93,7 +93,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
return ( return (
<> <form noValidate onSubmit={ handleSubmit(onSubmit) }>
<Box marginBottom={ 5 }> <Box marginBottom={ 5 }>
<Controller <Controller
name="address" name="address"
...@@ -119,14 +119,14 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -119,14 +119,14 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Button
size="lg" size="lg"
onClick={ handleSubmit(onSubmit) } type="submit"
disabled={ !isValid } disabled={ !isValid }
isLoading={ pending } isLoading={ pending }
> >
{ data ? 'Save changes' : 'Add tag' } { data ? 'Save changes' : 'Add tag' }
</Button> </Button>
</Box> </Box>
</> </form>
); );
}; };
......
...@@ -92,7 +92,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => ...@@ -92,7 +92,7 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
}, [ errors, formBackgroundColor ]); }, [ errors, formBackgroundColor ]);
return ( return (
<> <form noValidate onSubmit={ handleSubmit(onSubmit) }>
<Box marginBottom={ 5 }> <Box marginBottom={ 5 }>
<Controller <Controller
name="transaction" name="transaction"
...@@ -118,14 +118,14 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => ...@@ -118,14 +118,14 @@ const TransactionForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) =>
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Button
size="lg" size="lg"
onClick={ handleSubmit(onSubmit) } type="submit"
disabled={ !isValid } disabled={ !isValid }
isLoading={ pending } isLoading={ pending }
> >
{ data ? 'Save changes' : 'Add tag' } { data ? 'Save changes' : 'Add tag' }
</Button> </Button>
</Box> </Box>
</> </form>
); );
}; };
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
GridItem, GridItem,
Text, Text,
HStack, HStack,
chakra,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
...@@ -151,7 +152,12 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -151,7 +152,12 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
}, [ changeToDataScreen ]); }, [ changeToDataScreen ]);
return ( return (
<Box width={{ base: 'auto', lg: `calc(100% - ${ ADDRESS_INPUT_BUTTONS_WIDTH }px)` }} maxWidth="844px"> <chakra.form
noValidate
width={{ base: 'auto', lg: `calc(100% - ${ ADDRESS_INPUT_BUTTONS_WIDTH }px)` }}
maxWidth="844px"
onSubmit={ handleSubmit(onSubmit) }
>
{ isAlertVisible && <Box mb={ 4 }><FormSubmitAlert/></Box> } { isAlertVisible && <Box mb={ 4 }><FormSubmitAlert/></Box> }
<Text size="sm" variant="secondary" paddingBottom={ 5 }>Company info</Text> <Text size="sm" variant="secondary" paddingBottom={ 5 }>Company info</Text>
<Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 5 }> <Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 5 }>
...@@ -230,7 +236,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -230,7 +236,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
<HStack spacing={ 6 }> <HStack spacing={ 6 }>
<Button <Button
size="lg" size="lg"
onClick={ handleSubmit(onSubmit) } type="submit"
disabled={ !isValid } disabled={ !isValid }
isLoading={ mutation.isLoading } isLoading={ mutation.isLoading }
> >
...@@ -245,7 +251,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => { ...@@ -245,7 +251,7 @@ const PublicTagsForm = ({ changeToDataScreen, data }: Props) => {
Cancel Cancel
</Button> </Button>
</HStack> </HStack>
</Box> </chakra.form>
); );
}; };
......
...@@ -6,7 +6,7 @@ import filterIcon from 'icons/filter.svg'; ...@@ -6,7 +6,7 @@ import filterIcon from 'icons/filter.svg';
const FilterIcon = <Icon as={ filterIcon } boxSize={ 5 } mr={{ base: 0, lg: 2 }}/>; const FilterIcon = <Icon as={ filterIcon } boxSize={ 5 } mr={{ base: 0, lg: 2 }}/>;
interface Props { interface Props {
isActive: boolean; isActive?: boolean;
appliedFiltersNum?: number; appliedFiltersNum?: number;
onClick: () => void; onClick: () => void;
} }
......
import { Input, InputGroup, InputLeftElement, Icon, useColorModeValue, chakra } from '@chakra-ui/react'; import { chakra, Icon, IconButton, Input, InputGroup, InputLeftElement, InputRightElement, useColorModeValue } from '@chakra-ui/react';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import crossIcon from 'icons/cross.svg';
import searchIcon from 'icons/search.svg'; import searchIcon from 'icons/search.svg';
type Props = { type Props = {
...@@ -13,6 +14,7 @@ type Props = { ...@@ -13,6 +14,7 @@ type Props = {
const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) => { const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) => {
const [ filterQuery, setFilterQuery ] = useState(''); const [ filterQuery, setFilterQuery ] = useState('');
const inputRef = React.useRef<HTMLInputElement>(null);
const handleFilterQueryChange = useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleFilterQueryChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const { value } = event.target; const { value } = event.target;
...@@ -21,6 +23,12 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) = ...@@ -21,6 +23,12 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) =
onChange(value); onChange(value);
}, [ onChange ]); }, [ onChange ]);
const handleFilterQueryClear = useCallback(() => {
setFilterQuery('');
onChange('');
inputRef?.current?.focus();
}, [ onChange ]);
return ( return (
<InputGroup <InputGroup
size={ size } size={ size }
...@@ -33,6 +41,7 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) = ...@@ -33,6 +41,7 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) =
</InputLeftElement> </InputLeftElement>
<Input <Input
ref={ inputRef }
size={ size } size={ size }
value={ filterQuery } value={ filterQuery }
onChange={ handleFilterQueryChange } onChange={ handleFilterQueryChange }
...@@ -40,6 +49,19 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) = ...@@ -40,6 +49,19 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder }: Props) =
borderWidth="2px" borderWidth="2px"
textOverflow="ellipsis" textOverflow="ellipsis"
/> />
<InputRightElement>
<IconButton
colorScheme="gray"
aria-label="Clear the filter input"
title="Clear the filter input"
w={ 6 }
h={ 6 }
icon={ <Icon as={ crossIcon } w={ 4 } h={ 4 } color={ useColorModeValue('blackAlpha.600', 'whiteAlpha.600') }/> }
size="sm"
onClick={ handleFilterQueryClear }
/>
</InputRightElement>
</InputGroup> </InputGroup>
); );
}; };
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import FormSubmitAlert from 'ui/shared/FormSubmitAlert'; import FormSubmitAlert from 'ui/shared/FormSubmitAlert';
interface Props<TData> { interface Props<TData> {
...@@ -38,8 +39,10 @@ export default function FormModal<TData>({ ...@@ -38,8 +39,10 @@ export default function FormModal<TData>({
onClose(); onClose();
}, [ onClose, setAlertVisible ]); }, [ onClose, setAlertVisible ]);
const isMobile = useIsMobile();
return ( return (
<Modal isOpen={ isOpen } onClose={ onModalClose } size={{ base: 'full', lg: 'md' }}> <Modal isOpen={ isOpen } onClose={ onModalClose } size={ isMobile ? 'full' : 'md' }>
<ModalOverlay/> <ModalOverlay/>
<ModalContent> <ModalContent>
<ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader> <ModalHeader fontWeight="500" textStyle="h3">{ title }</ModalHeader>
......
import { Hide, Show, Text, Flex, Skeleton } from '@chakra-ui/react'; import { Hide, Show, Text } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import type { QueryKeys } from 'types/client/queries'; import type { QueryKeys } from 'types/client/queries';
import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import EmptySearchResult from 'ui/apps/EmptySearchResult';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import HashStringShorten from 'ui/shared/HashStringShorten';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import SkeletonTable from 'ui/shared/SkeletonTable'; import SkeletonTable from 'ui/shared/SkeletonTable';
import { flattenTotal } from 'ui/shared/TokenTransfer/helpers'; import { flattenTotal } from 'ui/shared/TokenTransfer/helpers';
import TokenTransferFilter from 'ui/shared/TokenTransfer/TokenTransferFilter';
import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList'; import TokenTransferList from 'ui/shared/TokenTransfer/TokenTransferList';
import TokenTransferSkeletonMobile from 'ui/shared/TokenTransfer/TokenTransferSkeletonMobile'; import TokenTransferSkeletonMobile from 'ui/shared/TokenTransfer/TokenTransferSkeletonMobile';
import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable'; import TokenTransferTable from 'ui/shared/TokenTransfer/TokenTransferTable';
...@@ -25,29 +28,34 @@ interface Props { ...@@ -25,29 +28,34 @@ interface Props {
txHash?: string; txHash?: string;
} }
const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryIds, path, baseAddress, showTxInfo = true, txHash }: Props) => { const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryIds, path, baseAddress, showTxInfo = true }: Props) => {
const [ filters, setFilters ] = React.useState<Array<TokenType>>([]);
const { isError, isLoading, data, pagination } = useQueryWithPages({ const { isError, isLoading, data, pagination } = useQueryWithPages({
apiPath: path, apiPath: path,
queryName, queryName,
queryIds, queryIds,
options: { enabled: !isDisabled }, options: { enabled: !isDisabled },
filters: filters.length ? { type: filters } : undefined,
}); });
const isPaginatorHidden = pagination.page === 1 && !pagination.hasNextPage; const handleFilterChange = React.useCallback((nextValue: Array<TokenType>) => {
setFilters(nextValue);
}, []);
const isActionBarHidden = filters.length === 0 && !data?.items.length;
const content = (() => { const content = (() => {
if (isLoading || isLoadingProp) { if (isLoading || isLoadingProp) {
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg">
{ txHash !== undefined && <Skeleton mb={ 6 } h={ 6 } w="340px"/> }
<SkeletonTable columns={ showTxInfo ? <SkeletonTable columns={ showTxInfo ?
[ '44px', '185px', '160px', '25%', '25%', '25%', '25%' ] : [ '44px', '185px', '160px', '25%', '25%', '25%', '25%' ] :
[ '185px', '25%', '25%', '25%', '25%' ] } [ '185px', '25%', '25%', '25%', '25%' ] }
/> />
</Hide> </Hide>
<Show below="lg"> <Show below="lg">
<TokenTransferSkeletonMobile showTxInfo={ showTxInfo } txHash={ txHash }/> <TokenTransferSkeletonMobile showTxInfo={ showTxInfo }/>
</Show> </Show>
</> </>
); );
...@@ -57,15 +65,19 @@ const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryI ...@@ -57,15 +65,19 @@ const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryI
return <DataFetchAlert/>; return <DataFetchAlert/>;
} }
if (!data.items?.length) { if (!data.items?.length && filters.length === 0) {
return <Text as="span">There are no token transfers</Text>; return <Text as="span">There are no token transfers</Text>;
} }
if (!data.items?.length) {
return <EmptySearchResult text={ `Couldn${ apos }t find any token transfer that matches your query.` }/>;
}
const items = data.items.reduce(flattenTotal, []); const items = data.items.reduce(flattenTotal, []);
return ( return (
<> <>
<Hide below="lg"> <Hide below="lg">
<TokenTransferTable data={ items } baseAddress={ baseAddress } showTxInfo={ showTxInfo } top={ isPaginatorHidden ? 0 : 80 }/> <TokenTransferTable data={ items } baseAddress={ baseAddress } showTxInfo={ showTxInfo } top={ 80 }/>
</Hide> </Hide>
<Show below="lg"> <Show below="lg">
<TokenTransferList data={ items } baseAddress={ baseAddress } showTxInfo={ showTxInfo }/> <TokenTransferList data={ items } baseAddress={ baseAddress } showTxInfo={ showTxInfo }/>
...@@ -76,14 +88,9 @@ const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryI ...@@ -76,14 +88,9 @@ const TokenTransfer = ({ isLoading: isLoadingProp, isDisabled, queryName, queryI
return ( return (
<> <>
{ txHash && (data?.items.length || 0 > 0) && ( { !isActionBarHidden && (
<Flex mb={ isPaginatorHidden ? 6 : 0 } w="100%"> <ActionBar mt={ -6 }>
<Text as="span" fontWeight={ 600 } whiteSpace="pre">Token transfers for by txn hash: </Text> <TokenTransferFilter defaultFilters={ filters } onFilterChange={ handleFilterChange } appliedFiltersNum={ filters.length }/>
<HashStringShorten hash={ txHash }/>
</Flex>
) }
{ isPaginatorHidden ? null : (
<ActionBar>
<Pagination ml="auto" { ...pagination }/> <Pagination ml="auto" { ...pagination }/>
</ActionBar> </ActionBar>
) } ) }
......
import { Popover, PopoverTrigger, PopoverContent, PopoverBody, CheckboxGroup, Checkbox, Text, useDisclosure } from '@chakra-ui/react';
import React from 'react';
import type { TokenType } from 'types/api/tokenInfo';
import FilterButton from 'ui/shared/FilterButton';
import { TOKEN_TYPE } from './helpers';
interface Props {
appliedFiltersNum?: number;
defaultFilters: Array<TokenType>;
onFilterChange: (nextValue: Array<TokenType>) => void;
}
const TokenTransfer = ({ onFilterChange, defaultFilters, appliedFiltersNum }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<FilterButton
isActive={ isOpen || Number(appliedFiltersNum) > 0 }
onClick={ onToggle }
appliedFiltersNum={ appliedFiltersNum }
/>
</PopoverTrigger>
<PopoverContent w="200px">
<PopoverBody px={ 4 } py={ 6 } display="flex" flexDir="column" rowGap={ 5 }>
<Text variant="secondary" fontWeight={ 600 }>Type</Text>
<CheckboxGroup size="lg" onChange={ onFilterChange } defaultValue={ defaultFilters }>
{ TOKEN_TYPE.map(({ title, id }) => <Checkbox key={ id } value={ id }><Text fontSize="md">{ title }</Text></Checkbox>) }
</CheckboxGroup>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default React.memo(TokenTransfer);
import { Skeleton, SkeletonCircle, Flex, Box, useColorModeValue } from '@chakra-ui/react'; import { Skeleton, SkeletonCircle, Flex, Box, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
const TokenTransferSkeletonMobile = ({ showTxInfo, txHash }: { showTxInfo?: boolean; txHash?: string }) => { const TokenTransferSkeletonMobile = ({ showTxInfo }: { showTxInfo?: boolean }) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return ( return (
<> <Box>
{ txHash !== undefined && <Skeleton mb={ 6 } h={ 6 } w="100%"/> } { Array.from(Array(2)).map((item, index) => (
<Box> <Flex
{ Array.from(Array(2)).map((item, index) => ( key={ index }
<Flex rowGap={ 3 }
key={ index } flexDirection="column"
rowGap={ 3 } paddingY={ 6 }
flexDirection="column" borderTopWidth="1px"
paddingY={ 6 } borderColor={ borderColor }
borderTopWidth="1px" _last={{
borderColor={ borderColor } borderBottomWidth: '1px',
_last={{ }}
borderBottomWidth: '1px', >
}} <Flex h={ 6 }>
> <SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Flex h={ 6 }> <Skeleton w="100px" mr={ 2 }/>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/> <Skeleton w="50px"/>
<Skeleton w="100px" mr={ 2 }/> { showTxInfo && <Skeleton w="24px" ml="auto"/> }
<Skeleton w="50px"/> </Flex>
{ showTxInfo && <Skeleton w="24px" ml="auto"/> } <Flex h={ 6 } columnGap={ 2 }>
</Flex> <Skeleton w="70px"/>
<Flex h={ 6 } columnGap={ 2 }> <Skeleton w="24px"/>
<Skeleton w="70px"/> <Skeleton w="90px"/>
<Skeleton w="24px"/> </Flex>
<Skeleton w="90px"/> { showTxInfo && (
</Flex>
{ showTxInfo && (
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="70px" flexShrink={ 0 }/>
<Skeleton w="100%"/>
</Flex>
) }
<Flex h={ 6 }>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
<Skeleton w="50px" mr={ 3 }/>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }> <Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="45px"/> <Skeleton w="70px" flexShrink={ 0 }/>
<Skeleton w="90px"/> <Skeleton w="100%"/>
</Flex> </Flex>
) }
<Flex h={ 6 }>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
<Skeleton w="50px" mr={ 3 }/>
<SkeletonCircle size="6" mr={ 2 } flexShrink={ 0 }/>
<Skeleton flexGrow={ 1 } mr={ 3 }/>
</Flex>
<Flex h={ 6 } columnGap={ 2 }>
<Skeleton w="45px"/>
<Skeleton w="90px"/>
</Flex> </Flex>
)) } </Flex>
</Box> )) }
</> </Box>
); );
}; };
......
import type { TokenType } from 'types/api/tokenInfo';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
export const flattenTotal = (result: Array<TokenTransfer>, item: TokenTransfer): Array<TokenTransfer> => { export const flattenTotal = (result: Array<TokenTransfer>, item: TokenTransfer): Array<TokenTransfer> => {
...@@ -24,3 +25,9 @@ export const getTokenTransferTypeText = (type: TokenTransfer['type']) => { ...@@ -24,3 +25,9 @@ export const getTokenTransferTypeText = (type: TokenTransfer['type']) => {
return 'Token transfer'; return 'Token transfer';
} }
}; };
export const TOKEN_TYPE: Array<{ title: string; id: TokenType }> = [
{ title: 'ERC-20', id: 'ERC-20' },
{ title: 'ERC-721', id: 'ERC-721' },
{ title: 'ERC-1155', id: 'ERC-1155' },
];
...@@ -17,7 +17,7 @@ interface Props { ...@@ -17,7 +17,7 @@ interface Props {
target?: HTMLAttributeAnchorTarget; target?: HTMLAttributeAnchorTarget;
} }
const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight, target }: Props) => { const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, fontWeight, target = '_self' }: Props) => {
let url; let url;
if (type === 'transaction') { if (type === 'transaction') {
url = link('tx', { id: id || hash }); url = link('tx', { id: id || hash });
...@@ -53,7 +53,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id, ...@@ -53,7 +53,7 @@ const AddressLink = ({ alias, type, className, truncation = 'dynamic', hash, id,
<Link <Link
className={ className } className={ className }
href={ url } href={ url }
target={ target || '_blank' } target={ target }
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
> >
......
...@@ -86,11 +86,19 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, anchorEl, ...props ...@@ -86,11 +86,19 @@ const ChartTooltip = ({ xScale, yScale, width, height, data, anchorEl, ...props
.selectAll('.ChartTooltip__point') .selectAll('.ChartTooltip__point')
.attr('transform', (cur, i) => { .attr('transform', (cur, i) => {
const index = bisectDate(data[i].items, xDate, 1); const index = bisectDate(data[i].items, xDate, 1);
const d0 = data[i].items[index - 1]; const d0 = data[i].items[index - 1] as TimeChartItem | undefined;
const d1 = data[i].items[index]; const d1 = data[i].items[index] as TimeChartItem | undefined;
const d = xDate.getTime() - d0?.date.getTime() > d1?.date.getTime() - xDate.getTime() ? d1 : d0; const d = (() => {
if (!d0) {
if (d.date === undefined && d.value === undefined) { return d1;
}
if (!d1) {
return d0;
}
return xDate.getTime() - d0.date.getTime() > d1.date.getTime() - xDate.getTime() ? d1 : d0;
})();
if (d?.date === undefined && d?.value === undefined) {
// move point out of container // move point out of container
return 'translate(-100,-100)'; return 'translate(-100,-100)';
} }
......
...@@ -51,7 +51,7 @@ const NavFooter = ({ isCollapsed, hasAccount }: Props) => { ...@@ -51,7 +51,7 @@ const NavFooter = ({ isCollapsed, hasAccount }: Props) => {
<Stack direction={{ base: 'row', lg: isExpanded ? 'row' : 'column', xl: isCollapsed ? 'column' : 'row' }}> <Stack direction={{ base: 'row', lg: isExpanded ? 'row' : 'column', xl: isCollapsed ? 'column' : 'row' }}>
{ SOCIAL_LINKS.map(sl => { { SOCIAL_LINKS.map(sl => {
return ( return (
<Link href={ sl.link } key={ sl.link } variant="secondary" w={ 5 } h={ 5 } aria-label={ sl.label }> <Link href={ sl.link } key={ sl.link } variant="secondary" w={ 5 } h={ 5 } aria-label={ sl.label } target="_blank">
<Icon as={ sl.icon } boxSize={ 5 }/> <Icon as={ sl.icon } boxSize={ 5 }/>
</Link> </Link>
); );
......
...@@ -58,7 +58,7 @@ const TxDetails = () => { ...@@ -58,7 +58,7 @@ const TxDetails = () => {
...data.from.watchlist_names || [], ...data.from.watchlist_names || [],
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>); ].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
const toAddress = data.to && data.to.hash ? data.to : data.created_contract; const toAddress = data.to ? data.to : data.created_contract;
const addressToTags = [ const addressToTags = [
...toAddress.private_tags || [], ...toAddress.private_tags || [],
...toAddress.public_tags || [], ...toAddress.public_tags || [],
......
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import type { TransactionRevertReason } from 'types/api/transaction'; import type { TransactionRevertReason } from 'types/api/transaction';
import hexToUtf8 from 'lib/hexToUtf8';
import TxDecodedInputData from 'ui/tx/TxDecodedInputData/TxDecodedInputData'; import TxDecodedInputData from 'ui/tx/TxDecodedInputData/TxDecodedInputData';
type Props = TransactionRevertReason; type Props = TransactionRevertReason;
...@@ -25,7 +26,7 @@ const TxRevertReason = (props: Props) => { ...@@ -25,7 +26,7 @@ const TxRevertReason = (props: Props) => {
<GridItem fontWeight={ 500 }>Raw:</GridItem> <GridItem fontWeight={ 500 }>Raw:</GridItem>
<GridItem>{ props.raw }</GridItem> <GridItem>{ props.raw }</GridItem>
<GridItem fontWeight={ 500 }>Decoded:</GridItem> <GridItem fontWeight={ 500 }>Decoded:</GridItem>
<GridItem>{ props.decoded }</GridItem> <GridItem>{ hexToUtf8(props.raw) }</GridItem>
</Grid> </Grid>
); );
} }
......
...@@ -17,7 +17,7 @@ type Props = InternalTransaction; ...@@ -17,7 +17,7 @@ type Props = InternalTransaction;
const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit: gasLimit, created_contract: createdContract }: Props) => { const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit: gasLimit, created_contract: createdContract }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const toData = to && to.hash ? to : createdContract; const toData = to ? to : createdContract;
return ( return (
<AccountListItemMobile rowGap={ 3 }> <AccountListItemMobile rowGap={ 3 }>
......
...@@ -15,7 +15,7 @@ type Props = InternalTransaction ...@@ -15,7 +15,7 @@ type Props = InternalTransaction
const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: gasLimit, created_contract: createdContract }: Props) => { const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit: gasLimit, created_contract: createdContract }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
const toData = to && to.hash ? to : createdContract; const toData = to ? to : createdContract;
return ( return (
<Tr alignItems="top"> <Tr alignItems="top">
......
...@@ -17,7 +17,6 @@ import useTxsSort from './useTxsSort'; ...@@ -17,7 +17,6 @@ import useTxsSort from './useTxsSort';
type Props = { type Props = {
queryName: QueryKeys.txsPending | QueryKeys.txsValidate | QueryKeys.blockTxs; queryName: QueryKeys.txsPending | QueryKeys.txsValidate | QueryKeys.blockTxs;
showDescription?: boolean;
stateFilter?: TTxsFilters['filter']; stateFilter?: TTxsFilters['filter'];
apiPath: string; apiPath: string;
showBlockInfo?: boolean; showBlockInfo?: boolean;
...@@ -25,7 +24,6 @@ type Props = { ...@@ -25,7 +24,6 @@ type Props = {
const TxsContent = ({ const TxsContent = ({
queryName, queryName,
showDescription,
stateFilter, stateFilter,
apiPath, apiPath,
showBlockInfo = true, showBlockInfo = true,
...@@ -81,7 +79,6 @@ const TxsContent = ({ ...@@ -81,7 +79,6 @@ const TxsContent = ({
return ( return (
<> <>
{ showDescription && <Box mb={{ base: 6, lg: 12 }}>Only the first 10,000 elements are displayed</Box> }
<TxsHeader mt={ -6 } sorting={ sorting } setSorting={ setSortByValue } paginationProps={ pagination } showPagination={ !isPaginatorHidden }/> <TxsHeader mt={ -6 } sorting={ sorting } setSorting={ setSortByValue } paginationProps={ pagination } showPagination={ !isPaginatorHidden }/>
{ content } { content }
</> </>
......
...@@ -33,7 +33,7 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo ...@@ -33,7 +33,7 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo
const iconColor = useColorModeValue('blue.600', 'blue.300'); const iconColor = useColorModeValue('blue.600', 'blue.300');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataTo = tx.to && tx.to.hash ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
return ( return (
<> <>
...@@ -59,7 +59,6 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo ...@@ -59,7 +59,6 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo
type="transaction" type="transaction"
fontWeight="700" fontWeight="700"
truncation="constant" truncation="constant"
target="_self"
/> />
</Address> </Address>
</Flex> </Flex>
......
...@@ -43,7 +43,7 @@ const TxsNewItemNotice = ({ children, className }: Props) => { ...@@ -43,7 +43,7 @@ const TxsNewItemNotice = ({ children, className }: Props) => {
<Alert className={ className } status="warning" p={ 4 } fontWeight={ 400 }> <Alert className={ className } status="warning" p={ 4 } fontWeight={ 400 }>
<Spinner size="sm" mr={ 3 }/> <Spinner size="sm" mr={ 3 }/>
<Text as="span" whiteSpace="pre">+ { num } new transaction{ num > 1 ? 's' : '' }. </Text> <Text as="span" whiteSpace="pre">+ { num } new transaction{ num > 1 ? 's' : '' }. </Text>
<Link onClick={ handleClick }>Show in list</Link> <Link onClick={ handleClick }>View in list</Link>
</Alert> </Alert>
); );
})(); })();
......
...@@ -13,7 +13,6 @@ const TxsTab = ({ tab }: Props) => { ...@@ -13,7 +13,6 @@ const TxsTab = ({ tab }: Props) => {
return ( return (
<TxsContent <TxsContent
queryName={ QueryKeys.txsValidate } queryName={ QueryKeys.txsValidate }
showDescription
stateFilter="validated" stateFilter="validated"
apiPath="/node-api/transactions" apiPath="/node-api/transactions"
/> />
......
...@@ -44,7 +44,7 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo ...@@ -44,7 +44,7 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo
</Address> </Address>
); );
const dataTo = tx.to && tx.to.hash ? tx.to : tx.created_contract; const dataTo = tx.to ? tx.to : tx.created_contract;
const addressTo = ( const addressTo = (
<Address> <Address>
...@@ -87,7 +87,6 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo ...@@ -87,7 +87,6 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo
hash={ tx.hash } hash={ tx.hash }
type="transaction" type="transaction"
fontWeight="700" fontWeight="700"
target="_self"
/> />
</Address> </Address>
<Text color="gray.500" fontWeight="400">{ dayjs(tx.timestamp).fromNow() }</Text> <Text color="gray.500" fontWeight="400">{ dayjs(tx.timestamp).fromNow() }</Text>
......
...@@ -151,7 +151,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -151,7 +151,7 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
), []); ), []);
return ( return (
<> <form noValidate onSubmit={ handleSubmit(onSubmit) }>
<Box marginBottom={ 5 }> <Box marginBottom={ 5 }>
<Controller <Controller
name="address" name="address"
...@@ -189,14 +189,14 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -189,14 +189,14 @@ const AddressForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
<Box marginTop={ 8 }> <Box marginTop={ 8 }>
<Button <Button
size="lg" size="lg"
onClick={ handleSubmit(onSubmit) } type="submit"
isLoading={ pending } isLoading={ pending }
disabled={ !isValid } disabled={ !isValid }
> >
{ data ? 'Save changes' : 'Add address' } { data ? 'Save changes' : 'Add address' }
</Button> </Button>
</Box> </Box>
</> </form>
); );
}; };
......
...@@ -20,9 +20,9 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => { ...@@ -20,9 +20,9 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
<VStack spacing={ 2 } align="stretch" fontWeight={ 500 } color="gray.700"> <VStack spacing={ 2 } align="stretch" fontWeight={ 500 } color="gray.700">
<AddressSnippet address={ item.address_hash }/> <AddressSnippet address={ item.address_hash }/>
<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.nativeTokenAddress && ( { appConfig.network.currency.address && (
<TokenLogo <TokenLogo
hash={ appConfig.network.nativeTokenAddress } hash={ appConfig.network.currency.address }
name={ appConfig.network.name } name={ appConfig.network.name }
boxSize={ 4 } boxSize={ 4 }
borderRadius="sm" borderRadius="sm"
......
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