Commit 5b1871cc authored by Max Alekseenko's avatar Max Alekseenko

Merge branch 'main' into rework-marketplace-app-header

parents 847ec0a2 b451db80
import app from './app'; import app from './app';
import { getEnvValue, getExternalAssetFilePath } from './utils'; import { getEnvValue, getExternalAssetFilePath } from './utils';
const defaultImageUrl = app.baseUrl + '/static/og_placeholder.png'; const defaultImageUrl = '/static/og_placeholder.png';
const meta = Object.freeze({ const meta = Object.freeze({
promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') || 'true', promoteBlockscoutInTitle: getEnvValue('NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE') || 'true',
og: { og: {
description: getEnvValue('NEXT_PUBLIC_OG_DESCRIPTION') || '', description: getEnvValue('NEXT_PUBLIC_OG_DESCRIPTION') || '',
imageUrl: getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl, imageUrl: app.baseUrl + (getExternalAssetFilePath('NEXT_PUBLIC_OG_IMAGE_URL') || defaultImageUrl),
}, },
}); });
......
...@@ -29,6 +29,7 @@ import type { ...@@ -29,6 +29,7 @@ import type {
AddressNFTsResponse, AddressNFTsResponse,
AddressCollectionsResponse, AddressCollectionsResponse,
AddressNFTTokensFilter, AddressNFTTokensFilter,
AddressCoinBalanceHistoryChartOld,
} from 'types/api/address'; } from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses'; import type { AddressesResponse } from 'types/api/addresses';
import type { TxBlobs, Blob } from 'types/api/blobs'; import type { TxBlobs, Blob } from 'types/api/blobs';
...@@ -859,7 +860,7 @@ Q extends 'address_internal_txs' ? AddressInternalTxsResponse : ...@@ -859,7 +860,7 @@ Q extends 'address_internal_txs' ? AddressInternalTxsResponse :
Q extends 'address_token_transfers' ? AddressTokenTransferResponse : Q extends 'address_token_transfers' ? AddressTokenTransferResponse :
Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse : Q extends 'address_blocks_validated' ? AddressBlocksValidatedResponse :
Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse : Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart : Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChartOld | AddressCoinBalanceHistoryChart :
Q extends 'address_logs' ? LogsResponseAddress : Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse : Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'address_nfts' ? AddressNFTsResponse : Q extends 'address_nfts' ? AddressNFTsResponse :
......
import { useEffect } from 'react'; import { useEffect } from 'react';
import type { AdBannerProviders } from 'types/client/adProviders';
import config from 'configs/app';
import { useAppContext } from 'lib/contexts/app'; import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
const DEFAULT_URL = 'https://request-global.czilladx.com';
// in general, detect should work with any ad-provider url (that is alive)
// but we see some false-positive results in certain browsers
const TEST_URLS: Record<AdBannerProviders, string> = {
slise: 'https://v1.slise.xyz/serve',
coinzilla: 'https://request-global.czilladx.com',
adbutler: 'https://servedbyadbutler.com/app.js',
hype: 'https://api.hypelab.com/v1/scripts/hp-sdk.js',
// I don't have an url for getit to test
getit: DEFAULT_URL,
none: DEFAULT_URL,
};
const feature = config.features.adsBanner;
export default function useAdblockDetect() { export default function useAdblockDetect() {
const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies);
const provider = feature.isEnabled && feature.provider;
useEffect(() => { useEffect(() => {
if (isBrowser() && !hasAdblockCookie) { if (isBrowser() && !hasAdblockCookie && provider) {
const url = 'https://request-global.czilladx.com'; const url = TEST_URLS[provider] || DEFAULT_URL;
fetch(url, { fetch(url, {
method: 'HEAD', method: 'HEAD',
mode: 'no-cors', mode: 'no-cors',
......
...@@ -35,33 +35,36 @@ export const baseResponse: AddressCoinBalanceHistoryResponse = { ...@@ -35,33 +35,36 @@ export const baseResponse: AddressCoinBalanceHistoryResponse = {
next_page_params: null, next_page_params: null,
}; };
export const chartResponse: AddressCoinBalanceHistoryChart = [ export const chartResponse: AddressCoinBalanceHistoryChart = {
{ items: [
date: '2022-11-02', {
value: '128238612887883515', date: '2022-11-02',
}, value: '128238612887883515',
{ },
date: '2022-11-03', {
value: '199807583157570922', date: '2022-11-03',
}, value: '199807583157570922',
{ },
date: '2022-11-04', {
value: '114487912907005778', date: '2022-11-04',
}, value: '114487912907005778',
{ },
date: '2022-11-05', {
value: '219533112907005778', date: '2022-11-05',
}, value: '219533112907005778',
{ },
date: '2022-11-06', {
value: '116487912907005778', date: '2022-11-06',
}, value: '116487912907005778',
{ },
date: '2022-11-07', {
value: '199807583157570922', date: '2022-11-07',
}, value: '199807583157570922',
{ },
date: '2022-11-08', {
value: '216488112907005778', date: '2022-11-08',
}, value: '216488112907005778',
]; },
],
days: 10,
};
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import type { Route } from 'nextjs-routes'; import type { Route } from 'nextjs-routes';
import config from 'configs/app';
import useAdblockDetect from 'lib/hooks/useAdblockDetect'; import useAdblockDetect from 'lib/hooks/useAdblockDetect';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import * as metadata from 'lib/metadata'; import * as metadata from 'lib/metadata';
...@@ -34,9 +35,14 @@ const PageNextJs = (props: Props) => { ...@@ -34,9 +35,14 @@ const PageNextJs = (props: Props) => {
<meta property="og:title" content={ opengraph.title }/> <meta property="og:title" content={ opengraph.title }/>
{ opengraph.description && <meta property="og:description" content={ opengraph.description }/> } { opengraph.description && <meta property="og:description" content={ opengraph.description }/> }
<meta property="og:image" content={ opengraph.imageUrl }/> <meta property="og:image" content={ opengraph.imageUrl }/>
<meta property="og:type" content="website"/>
{ /* Twitter Meta Tags */ }
<meta name="twitter:card" content="summary_large_image"/> <meta name="twitter:card" content="summary_large_image"/>
<meta property="twitter:domain" content={ config.app.host }/>
<meta name="twitter:title" content={ opengraph.title }/>
{ opengraph.description && <meta name="twitter:description" content={ opengraph.description }/> }
<meta property="twitter:image" content={ opengraph.imageUrl }/> <meta property="twitter:image" content={ opengraph.imageUrl }/>
<meta property="og:type" content="website"/>
</Head> </Head>
{ props.children } { props.children }
</> </>
......
...@@ -13,12 +13,16 @@ export function ad(): CspDev.DirectiveDescriptor { ...@@ -13,12 +13,16 @@ export function ad(): CspDev.DirectiveDescriptor {
'*.coinzilla.com', '*.coinzilla.com',
'https://request-global.czilladx.com', 'https://request-global.czilladx.com',
// adbutler
'servedbyadbutler.com',
// slise // slise
'*.slise.xyz', '*.slise.xyz',
// hype // hype
'api.hypelab.com', 'api.hypelab.com',
'*.ixncdn.com', '*.ixncdn.com',
'*.cloudfront.net',
//getit //getit
'v1.getittech.io', 'v1.getittech.io',
......
...@@ -148,11 +148,20 @@ export interface AddressCoinBalanceHistoryResponse { ...@@ -148,11 +148,20 @@ export interface AddressCoinBalanceHistoryResponse {
} | null; } | null;
} }
export type AddressCoinBalanceHistoryChart = Array<{ // remove after api release
export type AddressCoinBalanceHistoryChartOld = Array<{
date: string; date: string;
value: string; value: string;
}> }>
export type AddressCoinBalanceHistoryChart = {
items: Array<{
date: string;
value: string;
}>;
days: number;
};
export interface AddressBlocksValidatedResponse { export interface AddressBlocksValidatedResponse {
items: Array<Block>; items: Array<Block>;
next_page_params: { next_page_params: {
......
...@@ -20,6 +20,7 @@ export interface NovesClassificationData { ...@@ -20,6 +20,7 @@ export interface NovesClassificationData {
type: string | null; type: string | null;
}; };
message?: string; message?: string;
deployedContractAddress?: string;
} }
export interface Approved { export interface Approved {
......
...@@ -15,10 +15,17 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -15,10 +15,17 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
}); });
const items = React.useMemo(() => data?.map(({ date, value }) => ({ const items = React.useMemo(() => {
date: new Date(date), if (!data) {
value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(), return undefined;
})), [ data ]); }
const dataItems = 'items' in data ? data.items : data;
return dataItems.map(({ date, value }) => ({
date: new Date(date),
value: BigNumber(value).div(10 ** config.chain.currency.decimals).toNumber(),
}));
}, [ data ]);
return ( return (
<ChartWidget <ChartWidget
...@@ -28,6 +35,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => { ...@@ -28,6 +35,7 @@ const AddressCoinBalanceChart = ({ addressHash }: Props) => {
isLoading={ isPending } isLoading={ isPending }
h="300px" h="300px"
units={ currencyUnits.ether } units={ currencyUnits.ether }
emptyText={ data && 'days' in data && `Insufficient data for the past ${ data.days } days` }
/> />
); );
}; };
......
import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box } from '@chakra-ui/react'; import { Flex, Skeleton, Button, Grid, GridItem, Alert, Link, chakra, Box, useColorModeValue } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -16,6 +16,7 @@ import useSocketMessage from 'lib/socket/useSocketMessage'; ...@@ -16,6 +16,7 @@ import useSocketMessage from 'lib/socket/useSocketMessage';
import * as stubs from 'stubs/contract'; import * as stubs from 'stubs/contract';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import Hint from 'ui/shared/Hint';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
...@@ -34,11 +35,24 @@ type InfoItemProps = { ...@@ -34,11 +35,24 @@ type InfoItemProps = {
content: string | React.ReactNode; content: string | React.ReactNode;
className?: string; className?: string;
isLoading: boolean; isLoading: boolean;
hint?: string;
} }
const InfoItem = chakra(({ label, content, className, isLoading }: InfoItemProps) => ( const InfoItem = chakra(({ label, content, hint, className, isLoading }: InfoItemProps) => (
<GridItem display="flex" columnGap={ 6 } wordBreak="break-all" className={ className } alignItems="baseline"> <GridItem display="flex" columnGap={ 6 } wordBreak="break-all" className={ className } alignItems="baseline">
<Skeleton isLoaded={ !isLoading } w="170px" flexShrink={ 0 } fontWeight={ 500 }>{ label }</Skeleton> <Skeleton isLoaded={ !isLoading } w="170px" flexShrink={ 0 } fontWeight={ 500 }>
<Flex alignItems="center">
{ label }
{ hint && (
<Hint
label={ hint }
ml={ 2 }
color={ useColorModeValue('gray.600', 'gray.400') }
tooltipProps={{ placement: 'bottom' }}
/>
) }
</Flex>
</Skeleton>
<Skeleton isLoaded={ !isLoading }>{ content }</Skeleton> <Skeleton isLoaded={ !isLoading }>{ content }</Skeleton>
</GridItem> </GridItem>
)); ));
...@@ -251,7 +265,14 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { ...@@ -251,7 +265,14 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
{ data.name && <InfoItem label="Contract name" content={ data.name } isLoading={ isPlaceholderData }/> } { data.name && <InfoItem label="Contract name" content={ data.name } isLoading={ isPlaceholderData }/> }
{ data.compiler_version && <InfoItem label="Compiler version" content={ data.compiler_version } isLoading={ isPlaceholderData }/> } { data.compiler_version && <InfoItem label="Compiler version" content={ data.compiler_version } isLoading={ isPlaceholderData }/> }
{ data.evm_version && <InfoItem label="EVM version" content={ data.evm_version } textTransform="capitalize" isLoading={ isPlaceholderData }/> } { data.evm_version && <InfoItem label="EVM version" content={ data.evm_version } textTransform="capitalize" isLoading={ isPlaceholderData }/> }
{ licenseLink && <InfoItem label="License" content={ licenseLink } isLoading={ isPlaceholderData }/> } { licenseLink && (
<InfoItem
label="License"
content={ licenseLink }
hint="License type is informative field, and initial source code might have different license type from displayed."
isLoading={ isPlaceholderData }
/>
) }
{ typeof data.optimization_enabled === 'boolean' && { typeof data.optimization_enabled === 'boolean' &&
<InfoItem label="Optimization enabled" content={ data.optimization_enabled ? 'true' : 'false' } isLoading={ isPlaceholderData }/> } <InfoItem label="Optimization enabled" content={ data.optimization_enabled ? 'true' : 'false' } isLoading={ isPlaceholderData }/> }
{ data.optimization_runs && <InfoItem label="Optimization runs" content={ String(data.optimization_runs) } isLoading={ isPlaceholderData }/> } { data.optimization_runs && <InfoItem label="Optimization runs" content={ String(data.optimization_runs) } isLoading={ isPlaceholderData }/> }
......
import { Box, Button, chakra, Flex, Grid, Hide, Popover, PopoverBody, PopoverContent, PopoverTrigger, Show, Skeleton, useDisclosure } from '@chakra-ui/react'; import {
Box,
Button,
Flex,
Grid,
Hide,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
Show,
Skeleton,
useDisclosure,
chakra,
} from '@chakra-ui/react';
import _clamp from 'lodash/clamp'; import _clamp from 'lodash/clamp';
import React from 'react'; import React from 'react';
...@@ -12,6 +26,7 @@ import dayjs from 'lib/date/dayjs'; ...@@ -12,6 +26,7 @@ import dayjs from 'lib/date/dayjs';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity'; import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip';
interface Props { interface Props {
addressHash: string; addressHash: string;
...@@ -90,25 +105,27 @@ const AddressEnsDomains = ({ addressHash, mainDomainName }: Props) => { ...@@ -90,25 +105,27 @@ const AddressEnsDomains = ({ addressHash, mainDomainName }: Props) => {
return ( return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy> <Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger> <PopoverTrigger>
<Button <PopoverTriggerTooltip label="List of names resolved or owned by this address">
size="sm" <Button
variant="outline" size="sm"
colorScheme="gray" variant="outline"
onClick={ onToggle } colorScheme="gray"
aria-label="Address domains" onClick={ onToggle }
fontWeight={ 500 } aria-label="Address domains"
px={ 2 } fontWeight={ 500 }
h="32px" px={ 2 }
flexShrink={ 0 } h="32px"
> flexShrink={ 0 }
<IconSvg name="ENS_slim" boxSize={ 5 }/> >
<Show above="xl"> <IconSvg name="ENS_slim" boxSize={ 5 }/>
<chakra.span ml={ 1 }>{ totalRecords } Domain{ data.items.length > 1 ? 's' : '' }</chakra.span> <Show above="xl">
</Show> <chakra.span ml={ 1 }>{ totalRecords } Domain{ data.items.length > 1 ? 's' : '' }</chakra.span>
<Hide above="xl"> </Show>
<chakra.span ml={ 1 }>{ totalRecords }</chakra.span> <Hide above="xl">
</Hide> <chakra.span ml={ 1 }>{ totalRecords }</chakra.span>
</Button> </Hide>
</Button>
</PopoverTriggerTooltip>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent w={{ base: '100vw', lg: '500px' }}> <PopoverContent w={{ base: '100vw', lg: '500px' }}>
<PopoverBody px={ 6 } py={ 5 } fontSize="sm" display="flex" flexDir="column" rowGap={ 5 } alignItems="flex-start"> <PopoverBody px={ 6 } py={ 5 } fontSize="sm" display="flex" flexDir="column" rowGap={ 5 } alignItems="flex-start">
......
...@@ -66,7 +66,7 @@ const BlockPageContent = () => { ...@@ -66,7 +66,7 @@ const BlockPageContent = () => {
</> </>
), ),
}, },
blockQuery.data?.blob_tx_count ? config.features.dataAvailability.isEnabled && blockQuery.data?.blob_tx_count ?
{ {
id: 'blob_txs', id: 'blob_txs',
title: 'Blob txns', title: 'Blob txns',
......
...@@ -39,7 +39,7 @@ const TransactionPageContent = () => { ...@@ -39,7 +39,7 @@ const TransactionPageContent = () => {
const txQuery = useTxQuery(); const txQuery = useTxQuery();
const { data, isPlaceholderData, isError, error, errorUpdateCount } = txQuery; const { data, isPlaceholderData, isError, error, errorUpdateCount } = txQuery;
const showDegradedView = publicClient && (isError || isPlaceholderData) && errorUpdateCount > 0; const showDegradedView = publicClient && ((isError && error.status !== 422) || isPlaceholderData) && errorUpdateCount > 0;
const tabs: Array<RoutedTab> = (() => { const tabs: Array<RoutedTab> = (() => {
const detailsComponent = showDegradedView ? const detailsComponent = showDegradedView ?
...@@ -63,7 +63,7 @@ const TransactionPageContent = () => { ...@@ -63,7 +63,7 @@ const TransactionPageContent = () => {
{ id: 'user_ops', title: 'User operations', component: <TxUserOps txQuery={ txQuery }/> } : { id: 'user_ops', title: 'User operations', component: <TxUserOps txQuery={ txQuery }/> } :
undefined, undefined,
{ id: 'internal', title: 'Internal txns', component: <TxInternals txQuery={ txQuery }/> }, { id: 'internal', title: 'Internal txns', component: <TxInternals txQuery={ txQuery }/> },
txQuery.data?.blob_versioned_hashes?.length ? config.features.dataAvailability.isEnabled && txQuery.data?.blob_versioned_hashes?.length ?
{ id: 'blobs', title: 'Blobs', component: <TxBlobs txQuery={ txQuery }/> } : { id: 'blobs', title: 'Blobs', component: <TxBlobs txQuery={ txQuery }/> } :
undefined, undefined,
{ id: 'logs', title: 'Logs', component: <TxLogs txQuery={ txQuery }/> }, { id: 'logs', title: 'Logs', component: <TxLogs txQuery={ txQuery }/> },
......
...@@ -29,12 +29,6 @@ const Transactions = () => { ...@@ -29,12 +29,6 @@ const Transactions = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const tab = getQueryParamString(router.query.tab); const tab = getQueryParamString(router.query.tab);
React.useEffect(() => {
if (tab === 'blob_txs' && !config.features.dataAvailability.isEnabled) {
router.replace({ pathname: '/txs' }, undefined, { shallow: true });
}
}, [ router, tab ]);
const txsValidatedQuery = useQueryWithPages({ const txsValidatedQuery = useQueryWithPages({
resourceName: 'txs_validated', resourceName: 'txs_validated',
filters: { filter: 'validated' }, filters: { filter: 'validated' },
......
...@@ -20,6 +20,7 @@ import config from 'configs/app'; ...@@ -20,6 +20,7 @@ import config from 'configs/app';
import stripTrailingSlash from 'lib/stripTrailingSlash'; import stripTrailingSlash from 'lib/stripTrailingSlash';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal'; import LinkExternal from 'ui/shared/LinkExternal';
import PopoverTriggerTooltip from 'ui/shared/PopoverTriggerTooltip';
interface Props { interface Props {
className?: string; className?: string;
...@@ -55,26 +56,27 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => { ...@@ -55,26 +56,27 @@ const NetworkExplorers = ({ className, type, pathParam }: Props) => {
return ( return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy> <Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger> <PopoverTrigger>
<Button <PopoverTriggerTooltip label="Verify with other explorers" className={ className }>
className={ className } <Button
size="sm" size="sm"
variant="outline" variant="outline"
colorScheme="gray" colorScheme="gray"
onClick={ onToggle } onClick={ onToggle }
aria-label="Verify in other explorers" aria-label="Verify in other explorers"
fontWeight={ 500 } fontWeight={ 500 }
px={ 2 } px={ 2 }
h="32px" h="32px"
flexShrink={ 0 } flexShrink={ 0 }
> >
<IconSvg name="explorer" boxSize={ 5 }/> <IconSvg name="explorer" boxSize={ 5 }/>
<Show above="xl"> <Show above="xl">
<chakra.span ml={ 1 }>{ explorersLinks.length } Explorer{ explorersLinks.length > 1 ? 's' : '' }</chakra.span> <chakra.span ml={ 1 }>{ explorersLinks.length } Explorer{ explorersLinks.length > 1 ? 's' : '' }</chakra.span>
</Show> </Show>
<Hide above="xl"> <Hide above="xl">
<chakra.span ml={ 1 }>{ explorersLinks.length }</chakra.span> <chakra.span ml={ 1 }>{ explorersLinks.length }</chakra.span>
</Hide> </Hide>
</Button> </Button>
</PopoverTriggerTooltip>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent w="auto"> <PopoverContent w="auto">
<PopoverBody > <PopoverBody >
......
import { Skeleton, Tooltip, chakra } from '@chakra-ui/react';
import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
type Props = {
label: string;
isLoading?: boolean;
className?: string;
children: React.ReactNode;
}
const PopoverTriggerTooltip = ({ label, isLoading, className, children }: Props, ref: React.ForwardedRef<HTMLDivElement>) => {
const isMobile = useIsMobile();
return (
// tooltip need to be wrapped in div for proper popover positioning
<Skeleton isLoaded={ !isLoading } borderRadius="base" ref={ ref } className={ className }>
<Tooltip
label={ label }
isDisabled={ isMobile }
// need a delay to avoid flickering when closing the popover
openDelay={ 100 }
>
{ children }
</Tooltip>
</Skeleton>
);
};
export default chakra(React.forwardRef(PopoverTriggerTooltip));
...@@ -35,11 +35,12 @@ export type Props = { ...@@ -35,11 +35,12 @@ export type Props = {
isLoading: boolean; isLoading: boolean;
className?: string; className?: string;
isError: boolean; isError: boolean;
emptyText?: string;
} }
const DOWNLOAD_IMAGE_SCALE = 5; const DOWNLOAD_IMAGE_SCALE = 5;
const ChartWidget = ({ items, title, description, isLoading, className, isError, units }: Props) => { const ChartWidget = ({ items, title, description, isLoading, className, isError, units, emptyText }: Props) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [ isFullscreen, setIsFullscreen ] = useState(false); const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true); const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
...@@ -134,7 +135,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError, ...@@ -134,7 +135,7 @@ const ChartWidget = ({ items, title, description, isLoading, className, isError,
if (!hasItems) { if (!hasItems) {
return ( return (
<Center flexGrow={ 1 }> <Center flexGrow={ 1 }>
<Text variant="secondary" fontSize="sm">No data</Text> <Text variant="secondary" fontSize="sm">{ emptyText || 'No data' }</Text>
</Center> </Center>
); );
} }
......
import { Button, Skeleton } from '@chakra-ui/react'; import { Button } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import PopoverTriggerTooltip from '../PopoverTriggerTooltip';
import useScoreLevelAndColor from './useScoreLevelAndColor'; import useScoreLevelAndColor from './useScoreLevelAndColor';
interface Props { interface Props {
...@@ -21,10 +22,9 @@ const SolidityscanReportButton = ( ...@@ -21,10 +22,9 @@ const SolidityscanReportButton = (
const { scoreColor } = useScoreLevelAndColor(score); const { scoreColor } = useScoreLevelAndColor(score);
return ( return (
<Skeleton isLoaded={ !isLoading } borderRadius="base"> <PopoverTriggerTooltip label="Security score" isLoading={ isLoading } className={ className }>
<Button <Button
ref={ ref } ref={ ref }
className={ className }
color={ scoreColor } color={ scoreColor }
size="sm" size="sm"
variant="outline" variant="outline"
...@@ -39,7 +39,7 @@ const SolidityscanReportButton = ( ...@@ -39,7 +39,7 @@ const SolidityscanReportButton = (
<IconSvg name={ score < 80 ? 'score/score-not-ok' : 'score/score-ok' } boxSize={ 5 } mr={ onlyIcon ? 0 : 1 }/> <IconSvg name={ score < 80 ? 'score/score-not-ok' : 'score/score-ok' } boxSize={ 5 } mr={ onlyIcon ? 0 : 1 }/>
{ onlyIcon ? null : score } { onlyIcon ? null : score }
</Button> </Button>
</Skeleton> </PopoverTriggerTooltip>
); );
}; };
......
...@@ -57,6 +57,10 @@ function extractAddresses(data: NovesResponseData) { ...@@ -57,6 +57,10 @@ function extractAddresses(data: NovesResponseData) {
addressesSet.add({ hash: data.classificationData.approved.spender }); addressesSet.add({ hash: data.classificationData.approved.spender });
} }
if (data.classificationData.deployedContractAddress) {
addressesSet.add({ hash: data.classificationData.deployedContractAddress });
}
if (data.txTypeVersion === 2) { if (data.txTypeVersion === 2) {
data.classificationData.sent.forEach((transaction) => { data.classificationData.sent.forEach((transaction) => {
addressesSet.add({ hash: transaction.from.address, name: transaction.from.name }); addressesSet.add({ hash: transaction.from.address, name: transaction.from.name });
...@@ -72,5 +76,5 @@ function extractAddresses(data: NovesResponseData) { ...@@ -72,5 +76,5 @@ function extractAddresses(data: NovesResponseData) {
const addresses = Array.from(addressesSet) as Array<{hash: string; name?: string}>; // Convert Set to an array const addresses = Array.from(addressesSet) as Array<{hash: string; name?: string}>; // Convert Set to an array
// Remove empty and null values // Remove empty and null values
return addresses.filter(address => address.hash !== null && address.hash !== ''); return addresses.filter(address => address.hash !== null && address.hash !== '' && address.hash !== undefined);
} }
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import type { OptimisticL2WithdrawalStatus } from 'types/api/optimisticL2'; import type { OptimisticL2WithdrawalStatus } from 'types/api/optimisticL2';
import { WITHDRAWAL_STATUSES } from 'types/api/optimisticL2'; import { WITHDRAWAL_STATUSES } from 'types/api/optimisticL2';
import config from 'configs/app';
import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1'; import TxEntityL1 from 'ui/shared/entities/tx/TxEntityL1';
import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps'; import VerificationSteps from 'ui/shared/verificationSteps/VerificationSteps';
...@@ -12,8 +13,10 @@ interface Props { ...@@ -12,8 +13,10 @@ interface Props {
l1TxHash: string | undefined; l1TxHash: string | undefined;
} }
const rollupFeature = config.features.rollup;
const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => { const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
if (!status || !WITHDRAWAL_STATUSES.includes(status)) { if (!status || !WITHDRAWAL_STATUSES.includes(status) || !rollupFeature.isEnabled || rollupFeature.type !== 'optimistic') {
return null; return null;
} }
...@@ -46,7 +49,7 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => { ...@@ -46,7 +49,7 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
variant="outline" variant="outline"
size="sm" size="sm"
as="a" as="a"
href="https://app.optimism.io/bridge/withdraw" href={ rollupFeature.L2WithdrawalUrl }
target="_blank" target="_blank"
> >
Claim funds Claim funds
......
...@@ -8,7 +8,7 @@ import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem'; ...@@ -8,7 +8,7 @@ import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem';
const TxInternalsList = ({ data, isLoading }: { data: Array<InternalTransaction>; isLoading?: boolean }) => { const TxInternalsList = ({ data, isLoading }: { data: Array<InternalTransaction>; isLoading?: boolean }) => {
return ( return (
<Box> <Box>
{ data.map((item, index) => <TxInternalsListItem key={ item.transaction_hash + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>) } { data.map((item, index) => <TxInternalsListItem key={ item.index.toString() + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>) }
</Box> </Box>
); );
}; };
......
...@@ -44,7 +44,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) = ...@@ -44,7 +44,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle, top, isLoading }: Props) =
</Thead> </Thead>
<Tbody> <Tbody>
{ data.map((item, index) => ( { data.map((item, index) => (
<TxInternalsTableItem key={ item.transaction_hash + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/> <TxInternalsTableItem key={ item.index.toString() + (isLoading ? index : '') } { ...item } isLoading={ isLoading }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
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