Commit 9278adb7 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend

parents 60b34230 d7b9d940
...@@ -20,7 +20,17 @@ export const base2: Blob = { ...@@ -20,7 +20,17 @@ export const base2: Blob = {
], ],
}; };
export const withoutData: Blob = {
blob_data: null,
hash: '0x0197fdb17195c176b23160f335daabd4b6a231aaaadd73ec567877c66a3affd3',
kzg_commitment: null,
kzg_proof: null,
transaction_hashes: [
{ block_consensus: true, transaction_hash: '0x22d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193' },
],
};
export const txBlobs: TxBlobs = { export const txBlobs: TxBlobs = {
items: [ base1, base2 ], items: [ base1, base2, withoutData ],
next_page_params: null, next_page_params: null,
}; };
export interface TxBlob { export interface TxBlob {
hash: string; hash: string;
blob_data: string; blob_data: string | null;
kzg_commitment: string; kzg_commitment: string | null;
kzg_proof: string; kzg_proof: string | null;
} }
export type TxBlobs = { export type TxBlobs = {
......
...@@ -10,6 +10,7 @@ import ClearButton from 'ui/shared/ClearButton'; ...@@ -10,6 +10,7 @@ import ClearButton from 'ui/shared/ClearButton';
import ContractMethodFieldLabel from './ContractMethodFieldLabel'; import ContractMethodFieldLabel from './ContractMethodFieldLabel';
import ContractMethodMultiplyButton from './ContractMethodMultiplyButton'; import ContractMethodMultiplyButton from './ContractMethodMultiplyButton';
import useArgTypeMatchInt from './useArgTypeMatchInt'; import useArgTypeMatchInt from './useArgTypeMatchInt';
import useFormatFieldValue from './useFormatFieldValue';
import useValidateField from './useValidateField'; import useValidateField from './useValidateField';
interface Props { interface Props {
...@@ -29,15 +30,22 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -29,15 +30,22 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
const argTypeMatchInt = useArgTypeMatchInt({ argType: data.type }); const argTypeMatchInt = useArgTypeMatchInt({ argType: data.type });
const validate = useValidateField({ isOptional, argType: data.type, argTypeMatchInt }); const validate = useValidateField({ isOptional, argType: data.type, argTypeMatchInt });
const format = useFormatFieldValue({ argType: data.type, argTypeMatchInt });
const { control, setValue, getValues } = useFormContext(); const { control, setValue, getValues } = useFormContext();
const { field, fieldState } = useController({ control, name, rules: { validate, required: isOptional ? false : 'Field is required' } }); const { field, fieldState } = useController({ control, name, rules: { validate } });
const inputBgColor = useColorModeValue('white', 'black'); const inputBgColor = useColorModeValue('white', 'black');
const nativeCoinRowBgColor = useColorModeValue('gray.100', 'gray.700'); const nativeCoinRowBgColor = useColorModeValue('gray.100', 'gray.700');
const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64; const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64;
const handleChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const formattedValue = format(event.target.value);
field.onChange(formattedValue); // data send back to hook form
setValue(name, formattedValue); // UI state
}, [ field, name, setValue, format ]);
const handleClear = React.useCallback(() => { const handleClear = React.useCallback(() => {
setValue(name, ''); setValue(name, '');
ref.current?.focus(); ref.current?.focus();
...@@ -46,9 +54,9 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -46,9 +54,9 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
const handleMultiplyButtonClick = React.useCallback((power: number) => { const handleMultiplyButtonClick = React.useCallback((power: number) => {
const zeroes = Array(power).fill('0').join(''); const zeroes = Array(power).fill('0').join('');
const value = getValues(name); const value = getValues(name);
const newValue = value ? value + zeroes : '1' + zeroes; const newValue = format(value ? value + zeroes : '1' + zeroes);
setValue(name, newValue); setValue(name, newValue);
}, [ getValues, name, setValue ]); }, [ format, getValues, name, setValue ]);
const error = fieldState.error; const error = fieldState.error;
...@@ -76,6 +84,7 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -76,6 +84,7 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
allowNegative: !argTypeMatchInt.isUnsigned, allowNegative: !argTypeMatchInt.isUnsigned,
} : {}) } } : {}) }
ref={ ref } ref={ ref }
onChange={ handleChange }
required={ !isOptional } required={ !isOptional }
isInvalid={ Boolean(error) } isInvalid={ Boolean(error) }
placeholder={ data.type } placeholder={ data.type }
...@@ -84,7 +93,7 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi ...@@ -84,7 +93,7 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi
paddingRight={ hasMultiplyButton ? '120px' : '40px' } paddingRight={ hasMultiplyButton ? '120px' : '40px' }
/> />
<InputRightElement w="auto" right={ 1 }> <InputRightElement w="auto" right={ 1 }>
{ typeof field.value === 'string' && field.value.replace('\n', '') && <ClearButton onClick={ handleClear } isDisabled={ isDisabled }/> } { field.value !== undefined && field.value !== '' && <ClearButton onClick={ handleClear } isDisabled={ isDisabled }/> }
{ hasMultiplyButton && <ContractMethodMultiplyButton onClick={ handleMultiplyButtonClick } isDisabled={ isDisabled }/> } { hasMultiplyButton && <ContractMethodMultiplyButton onClick={ handleMultiplyButtonClick } isDisabled={ isDisabled }/> }
</InputRightElement> </InputRightElement>
</InputGroup> </InputGroup>
......
...@@ -20,11 +20,11 @@ const ContractMethodFormOutputs = ({ data }: Props) => { ...@@ -20,11 +20,11 @@ const ContractMethodFormOutputs = ({ data }: Props) => {
<p> <p>
{ data.map(({ type, name }, index) => { { data.map(({ type, name }, index) => {
return ( return (
<> <React.Fragment key={ index }>
<chakra.span fontWeight={ 500 }>{ name } </chakra.span> <chakra.span fontWeight={ 500 }>{ name } </chakra.span>
<span>{ name ? `(${ type })` : type }</span> <span>{ name ? `(${ type })` : type }</span>
{ index < data.length - 1 && <span>, </span> } { index < data.length - 1 && <span>, </span> }
</> </React.Fragment>
); );
}) } }) }
</p> </p>
......
import React from 'react';
import type { SmartContractMethodArgType } from 'types/api/contract';
import type { MatchInt } from './useArgTypeMatchInt';
interface Params {
argType: SmartContractMethodArgType;
argTypeMatchInt: MatchInt | null;
}
export default function useFormatFieldValue({ argType, argTypeMatchInt }: Params) {
return React.useCallback((value: string | undefined) => {
if (!value) {
return;
}
if (argTypeMatchInt) {
const formattedString = value.replace(/\s/g, '');
return parseInt(formattedString);
}
if (argType === 'bool') {
const formattedValue = value.toLowerCase();
switch (formattedValue) {
case 'true': {
return true;
}
case 'false':{
return false;
}
default:
return value;
}
}
return value;
}, [ argType, argTypeMatchInt ]);
}
...@@ -4,7 +4,7 @@ import { getAddress, isAddress, isHex } from 'viem'; ...@@ -4,7 +4,7 @@ import { getAddress, isAddress, isHex } from 'viem';
import type { SmartContractMethodArgType } from 'types/api/contract'; import type { SmartContractMethodArgType } from 'types/api/contract';
import type { MatchInt } from './useArgTypeMatchInt'; import type { MatchInt } from './useArgTypeMatchInt';
import { BYTES_REGEXP, formatBooleanValue } from './utils'; import { BYTES_REGEXP } from './utils';
interface Params { interface Params {
argType: SmartContractMethodArgType; argType: SmartContractMethodArgType;
...@@ -18,13 +18,15 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt ...@@ -18,13 +18,15 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt
return argType.match(BYTES_REGEXP); return argType.match(BYTES_REGEXP);
}, [ argType ]); }, [ argType ]);
return React.useCallback((value: string | undefined) => { // some values are formatted before they are sent to the validator
if (!value) { // see ./useFormatFieldValue.tsx hook
return React.useCallback((value: string | number | boolean | undefined) => {
if (value === undefined || value === '') {
return isOptional ? true : 'Field is required'; return isOptional ? true : 'Field is required';
} }
if (argType === 'address') { if (argType === 'address') {
if (!isAddress(value)) { if (typeof value !== 'string' || !isAddress(value)) {
return 'Invalid address format'; return 'Invalid address format';
} }
...@@ -39,13 +41,11 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt ...@@ -39,13 +41,11 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt
} }
if (argTypeMatchInt) { if (argTypeMatchInt) {
const formattedValue = Number(value.replace(/\s/g, '')); if (typeof value !== 'number' || Object.is(value, NaN)) {
if (Object.is(formattedValue, NaN)) {
return 'Invalid integer format'; return 'Invalid integer format';
} }
if (formattedValue > argTypeMatchInt.max || formattedValue < argTypeMatchInt.min) { if (value > argTypeMatchInt.max || value < argTypeMatchInt.min) {
const lowerBoundary = argTypeMatchInt.isUnsigned ? '0' : `-1 * 2 ^ ${ Number(argTypeMatchInt.power) - 1 }`; const lowerBoundary = argTypeMatchInt.isUnsigned ? '0' : `-1 * 2 ^ ${ Number(argTypeMatchInt.power) - 1 }`;
const upperBoundary = argTypeMatchInt.isUnsigned ? `2 ^ ${ argTypeMatchInt.power } - 1` : `2 ^ ${ Number(argTypeMatchInt.power) - 1 } - 1`; const upperBoundary = argTypeMatchInt.isUnsigned ? `2 ^ ${ argTypeMatchInt.power } - 1` : `2 ^ ${ Number(argTypeMatchInt.power) - 1 } - 1`;
return `Value should be in range from "${ lowerBoundary }" to "${ upperBoundary }" inclusively`; return `Value should be in range from "${ lowerBoundary }" to "${ upperBoundary }" inclusively`;
...@@ -55,9 +55,8 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt ...@@ -55,9 +55,8 @@ export default function useValidateField({ isOptional, argType, argTypeMatchInt
} }
if (argType === 'bool') { if (argType === 'bool') {
const formattedValue = formatBooleanValue(value); if (typeof value !== 'boolean') {
if (formattedValue === undefined) { return 'Invalid boolean format. Allowed values: true, false';
return 'Invalid boolean format. Allowed values: 0, 1, true, false';
} }
} }
......
...@@ -17,25 +17,6 @@ export const getIntBoundaries = (power: number, isUnsigned: boolean) => { ...@@ -17,25 +17,6 @@ export const getIntBoundaries = (power: number, isUnsigned: boolean) => {
return [ min, max ]; return [ min, max ];
}; };
export const formatBooleanValue = (value: string) => {
const formattedValue = value.toLowerCase();
switch (formattedValue) {
case 'true':
case '1': {
return 'true';
}
case 'false':
case '0': {
return 'false';
}
default:
return;
}
};
export function transformFormDataToMethodArgs(formData: ContractMethodFormFields) { export function transformFormDataToMethodArgs(formData: ContractMethodFormFields) {
const result: Array<unknown> = []; const result: Array<unknown> = [];
......
import { Grid, Skeleton } from '@chakra-ui/react'; import { Alert, Grid, GridItem, Skeleton } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Blob } from 'types/api/blobs'; import type { Blob } from 'types/api/blobs';
...@@ -17,45 +17,56 @@ interface Props { ...@@ -17,45 +17,56 @@ interface Props {
} }
const BlobInfo = ({ data, isLoading }: Props) => { const BlobInfo = ({ data, isLoading }: Props) => {
const size = data.blob_data.replace('0x', '').length / 2;
return ( return (
<Grid <Grid
columnGap={ 8 } columnGap={ 8 }
rowGap={ 3 } rowGap={ 3 }
templateColumns={{ base: 'minmax(0, 1fr)', lg: '216px minmax(728px, auto)' }} templateColumns={{ base: 'minmax(0, 1fr)', lg: '216px minmax(728px, auto)' }}
> >
<DetailsInfoItem { !data.blob_data && (
title="Proof" <GridItem colSpan={{ base: undefined, lg: 2 }} mb={ 3 }>
hint="Zero knowledge proof. Allows for quick verification of commitment" <Skeleton isLoaded={ !isLoading }>
isLoading={ isLoading } <Alert status="warning">This blob is not yet indexed</Alert>
> </Skeleton>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px"> </GridItem>
{ data.kzg_proof } ) }
<CopyToClipboard text={ data.kzg_proof } isLoading={ isLoading }/> { data.kzg_proof && (
</Skeleton> <DetailsInfoItem
</DetailsInfoItem> title="Proof"
<DetailsInfoItem hint="Zero knowledge proof. Allows for quick verification of commitment"
title="Commitment" isLoading={ isLoading }
hint="Commitment to the data in the blob" >
isLoading={ isLoading } <Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
> { data.kzg_proof }
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px"> <CopyToClipboard text={ data.kzg_proof } isLoading={ isLoading }/>
{ data.kzg_commitment } </Skeleton>
<CopyToClipboard text={ data.kzg_commitment } isLoading={ isLoading }/> </DetailsInfoItem>
</Skeleton> ) }
</DetailsInfoItem> { data.kzg_commitment && (
<DetailsInfoItem <DetailsInfoItem
title="Size, bytes" title="Commitment"
hint="Blob size in bytes" hint="Commitment to the data in the blob"
isLoading={ isLoading } isLoading={ isLoading }
> >
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all"> <Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all" lineHeight={ 6 } my="-2px">
{ size.toLocaleString() } { data.kzg_commitment }
</Skeleton> <CopyToClipboard text={ data.kzg_commitment } isLoading={ isLoading }/>
</DetailsInfoItem> </Skeleton>
</DetailsInfoItem>
) }
{ data.blob_data && (
<DetailsInfoItem
title="Size, bytes"
hint="Blob size in bytes"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } overflow="hidden" whiteSpace="pre-wrap" wordBreak="break-all">
{ (data.blob_data.replace('0x', '').length / 2).toLocaleString() }
</Skeleton>
</DetailsInfoItem>
) }
<DetailsInfoItemDivider/> { data.blob_data && <DetailsInfoItemDivider/> }
{ data.transaction_hashes[0] && ( { data.transaction_hashes[0] && (
<DetailsInfoItem <DetailsInfoItem
...@@ -68,9 +79,12 @@ const BlobInfo = ({ data, isLoading }: Props) => { ...@@ -68,9 +79,12 @@ const BlobInfo = ({ data, isLoading }: Props) => {
) } ) }
<DetailsSponsoredItem isLoading={ isLoading }/> <DetailsSponsoredItem isLoading={ isLoading }/>
<DetailsInfoItemDivider/> { data.blob_data && (
<>
<BlobData data={ data.blob_data } hash={ data.hash } isLoading={ isLoading }/> <DetailsInfoItemDivider/>
<BlobData data={ data.blob_data } hash={ data.hash } isLoading={ isLoading }/>
</>
) }
</Grid> </Grid>
); );
}; };
......
...@@ -19,7 +19,7 @@ const MarketplaceAppAlert = ({ internalWallet, isWalletConnected }: Props) => { ...@@ -19,7 +19,7 @@ const MarketplaceAppAlert = ({ internalWallet, isWalletConnected }: Props) => {
icon = 'integration/full'; icon = 'integration/full';
text = 'Your wallet is connected with Blockscout'; text = 'Your wallet is connected with Blockscout';
status = 'success'; status = 'success';
} else if (isWalletConnected) { } else if (!internalWallet) {
icon = 'integration/partial'; icon = 'integration/partial';
text = 'Connect your wallet in the app below'; text = 'Connect your wallet in the app below';
} }
......
...@@ -50,20 +50,23 @@ function sortApps(apps: Array<MarketplaceAppOverview>, favoriteApps: Array<strin ...@@ -50,20 +50,23 @@ function sortApps(apps: Array<MarketplaceAppOverview>, favoriteApps: Array<strin
export default function useMarketplaceApps( export default function useMarketplaceApps(
filter: string, filter: string,
selectedCategoryId: string = MarketplaceCategory.ALL, selectedCategoryId: string = MarketplaceCategory.ALL,
favoriteApps: Array<string> = [], favoriteApps: Array<string> | undefined = undefined,
isFavoriteAppsLoaded: boolean = false, // eslint-disable-line @typescript-eslint/no-inferrable-types isFavoriteAppsLoaded: boolean = false, // eslint-disable-line @typescript-eslint/no-inferrable-types
) { ) {
const fetch = useFetch(); const fetch = useFetch();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
// Update favorite apps only when selectedCategoryId changes to avoid sortApps to be called on each favorite app click // Update favorite apps only when selectedCategoryId changes to avoid sortApps to be called on each favorite app click
const lastFavoriteAppsRef = React.useRef(favoriteApps); const [ snapshotFavoriteApps, setSnapshotFavoriteApps ] = React.useState<Array<string> | undefined>();
React.useEffect(() => { React.useEffect(() => {
lastFavoriteAppsRef.current = favoriteApps; if (isFavoriteAppsLoaded) {
setSnapshotFavoriteApps(favoriteApps);
}
}, [ selectedCategoryId, isFavoriteAppsLoaded ]); // eslint-disable-line react-hooks/exhaustive-deps }, [ selectedCategoryId, isFavoriteAppsLoaded ]); // eslint-disable-line react-hooks/exhaustive-deps
const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>({ const { isPlaceholderData, isError, error, data } = useQuery<unknown, ResourceError<unknown>, Array<MarketplaceAppOverview>>({
queryKey: [ 'marketplace-dapps' ], queryKey: [ 'marketplace-dapps', snapshotFavoriteApps, favoriteApps ],
queryFn: async() => { queryFn: async() => {
if (!feature.isEnabled) { if (!feature.isEnabled) {
return []; return [];
...@@ -73,14 +76,14 @@ export default function useMarketplaceApps( ...@@ -73,14 +76,14 @@ export default function useMarketplaceApps(
return apiFetch('marketplace_dapps', { pathParams: { chainId: config.chain.id } }); return apiFetch('marketplace_dapps', { pathParams: { chainId: config.chain.id } });
} }
}, },
select: (data) => sortApps(data as Array<MarketplaceAppOverview>, lastFavoriteAppsRef.current), select: (data) => sortApps(data as Array<MarketplaceAppOverview>, snapshotFavoriteApps || []),
placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined, placeholderData: feature.isEnabled ? Array(9).fill(MARKETPLACE_APP) : undefined,
staleTime: Infinity, staleTime: Infinity,
enabled: feature.isEnabled, enabled: feature.isEnabled && (!favoriteApps || Boolean(snapshotFavoriteApps)),
}); });
const displayedApps = React.useMemo(() => { const displayedApps = React.useMemo(() => {
return data?.filter(app => isAppNameMatches(filter, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps)) || []; return data?.filter(app => isAppNameMatches(filter, app) && isAppCategoryMatches(selectedCategoryId, app, favoriteApps || [])) || [];
}, [ selectedCategoryId, data, filter, favoriteApps ]); }, [ selectedCategoryId, data, filter, favoriteApps ]);
return React.useMemo(() => ({ return React.useMemo(() => ({
......
...@@ -47,3 +47,22 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => { ...@@ -47,3 +47,22 @@ test('base view +@mobile +@dark-mode', async({ mount, page }) => {
maskColor: configs.maskColor, maskColor: configs.maskColor,
}); });
}); });
test('without data', async({ mount, page }) => {
await page.route(BLOB_API_URL, (route) => route.fulfill({
status: 200,
body: JSON.stringify(blobsMock.withoutData),
}));
const component = await mount(
<TestApp>
<Blob/>
</TestApp>,
{ hooksConfig },
);
await expect(component).toHaveScreenshot({
mask: [ page.locator(configs.adsBannerSelector) ],
maskColor: configs.maskColor,
});
});
...@@ -16,7 +16,7 @@ const ValidatorStatus = ({ state, isLoading }: Props) => { ...@@ -16,7 +16,7 @@ const ValidatorStatus = ({ state, isLoading }: Props) => {
case 'probation': case 'probation':
return <StatusTag type="pending" text="Probation" isLoading={ isLoading }/>; return <StatusTag type="pending" text="Probation" isLoading={ isLoading }/>;
case 'inactive': case 'inactive':
return <StatusTag type="error" text="Failed" isLoading={ isLoading }/>; return <StatusTag type="error" text="Inactive" isLoading={ isLoading }/>;
} }
}; };
......
...@@ -13,7 +13,7 @@ interface Props { ...@@ -13,7 +13,7 @@ interface Props {
} }
const TxBlobListItem = ({ data, isLoading }: Props) => { const TxBlobListItem = ({ data, isLoading }: Props) => {
const size = data.blob_data.replace('0x', '').length / 2; const size = data.blob_data ? data.blob_data.replace('0x', '').length / 2 : '-';
return ( return (
<ListItemMobileGrid.Container> <ListItemMobileGrid.Container>
...@@ -24,7 +24,7 @@ const TxBlobListItem = ({ data, isLoading }: Props) => { ...@@ -24,7 +24,7 @@ const TxBlobListItem = ({ data, isLoading }: Props) => {
<ListItemMobileGrid.Label isLoading={ isLoading }>Data type</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Data type</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value> <ListItemMobileGrid.Value>
<BlobDataType isLoading={ isLoading } data={ data.blob_data }/> { data.blob_data ? <BlobDataType isLoading={ isLoading } data={ data.blob_data }/> : '-' }
</ListItemMobileGrid.Value> </ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Size, bytes</ListItemMobileGrid.Label> <ListItemMobileGrid.Label isLoading={ isLoading }>Size, bytes</ListItemMobileGrid.Label>
......
...@@ -12,7 +12,7 @@ interface Props { ...@@ -12,7 +12,7 @@ interface Props {
} }
const TxBlobsTableItem = ({ data, isLoading }: Props) => { const TxBlobsTableItem = ({ data, isLoading }: Props) => {
const size = data.blob_data.replace('0x', '').length / 2; const size = data.blob_data ? data.blob_data.replace('0x', '').length / 2 : '-';
return ( return (
<Tr alignItems="top"> <Tr alignItems="top">
...@@ -20,7 +20,7 @@ const TxBlobsTableItem = ({ data, isLoading }: Props) => { ...@@ -20,7 +20,7 @@ const TxBlobsTableItem = ({ data, isLoading }: Props) => {
<BlobEntity hash={ data.hash } noIcon isLoading={ isLoading }/> <BlobEntity hash={ data.hash } noIcon isLoading={ isLoading }/>
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<BlobDataType isLoading={ isLoading } data={ data.blob_data }/> { data.blob_data ? <BlobDataType isLoading={ isLoading } data={ data.blob_data }/> : '-' }
</Td> </Td>
<Td verticalAlign="middle"> <Td verticalAlign="middle">
<Skeleton isLoaded={ !isLoading } display="inline-block"> <Skeleton isLoaded={ !isLoading } display="inline-block">
......
...@@ -36,7 +36,7 @@ const ValidatorsFilter = ({ onChange, defaultValue, isActive }: Props) => { ...@@ -36,7 +36,7 @@ const ValidatorsFilter = ({ onChange, defaultValue, isActive }: Props) => {
<MenuItemOption value="all">All</MenuItemOption> <MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="active">Active</MenuItemOption> <MenuItemOption value="active">Active</MenuItemOption>
<MenuItemOption value="probation">Probation</MenuItemOption> <MenuItemOption value="probation">Probation</MenuItemOption>
<MenuItemOption value="inactive">Failed</MenuItemOption> <MenuItemOption value="inactive">Inactive</MenuItemOption>
</MenuOptionGroup> </MenuOptionGroup>
</MenuList> </MenuList>
</Menu> </Menu>
......
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