Commit 7bfca7a4 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #145 from blockscout/tx-page-internal

tx page: internal txs tab
parents 2657c7a8 771473da
export const data = [
{
id: 1,
type: 'call',
status: 'success' as const,
from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01',
to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e',
value: 0.25207646303,
gasLimit: 369472,
},
{
id: 2,
type: 'delegate call',
status: 'error' as const,
from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01',
value: 0.5633333,
gasLimit: 340022,
},
{
id: 3,
type: 'static call',
status: 'success' as const,
from: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
to: '0x35317007D203b8a86CA727ad44E473E40450E378',
value: 0.421152366,
gasLimit: 509333,
},
];
export const data = [
{
address: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01',
topics: [
{ hex: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' },
{ hex: '0x000000000000000000000000def171fe48cf0115b1d80b88dc8eab59176fee57' },
{ hex: '0x000000000000000000000000c465c0a16228ef6fe1bf29c04fdb04bb797fd537' },
],
data: '0x000000000000000000000000000000000000000000000000019faae14eb88000',
},
{
address: '0x73968b9a57c6e53d41345fd57a6e6ae27d6cdb2f',
topics: [
{ hex: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' },
{ hex: '0x000000000000000000000000c465c0a16228ef6fe1bf29c04fdb04bb797fd537' },
{ hex: '0x0000000000000000000000008453d9385af5f49edad9905345cd2411b5c5831b' },
],
data: '0x000000000000000000000000000000000000000000000013b6ee62022c95ced4',
},
];
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.5 14.167c0 .46.373.833.833.833H11a.833.833 0 1 0 0-1.667H9.333a.833.833 0 0 0-.833.834ZM3.5 5a.833.833 0 0 0 0 1.667h13.333a.833.833 0 0 0 0-1.667H3.5Zm1.667 5c0 .46.373.833.833.833h8.333a.833.833 0 0 0 0-1.666H6a.833.833 0 0 0-.833.833Z" fill="currentColor"/>
</svg>
...@@ -13,6 +13,14 @@ import getDefaultTransitionProps from '../utils/getDefaultTransitionProps'; ...@@ -13,6 +13,14 @@ import getDefaultTransitionProps from '../utils/getDefaultTransitionProps';
import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles';
const size = { const size = {
xs: defineStyle({
fontSize: 'md',
lineHeight: '24px',
px: '4px',
py: '12px',
h: '32px',
borderRadius: 'base',
}),
sm: defineStyle({ sm: defineStyle({
fontSize: 'md', fontSize: 'md',
lineHeight: '24px', lineHeight: '24px',
...@@ -55,6 +63,10 @@ const variantOutline = definePartsStyle((props) => { ...@@ -55,6 +63,10 @@ const variantOutline = definePartsStyle((props) => {
}); });
const sizes = { const sizes = {
xs: definePartsStyle({
field: size.xs,
addon: size.xs,
}),
sm: definePartsStyle({ sm: definePartsStyle({
field: size.sm, field: size.sm,
addon: size.sm, addon: size.sm,
......
...@@ -12,6 +12,8 @@ import useBasePath from 'lib/hooks/useBasePath'; ...@@ -12,6 +12,8 @@ import useBasePath from 'lib/hooks/useBasePath';
import Page from 'ui/shared/Page'; import Page from 'ui/shared/Page';
import PageHeader from 'ui/shared/PageHeader'; import PageHeader from 'ui/shared/PageHeader';
import TxDetails from 'ui/tx/TxDetails'; import TxDetails from 'ui/tx/TxDetails';
import TxInternals from 'ui/tx/TxInternals';
import TxLogs from 'ui/tx/TxLogs';
interface Tab { interface Tab {
type: 'details' | 'internal_txn' | 'logs' | 'raw_trace' | 'state'; type: 'details' | 'internal_txn' | 'logs' | 'raw_trace' | 'state';
...@@ -22,8 +24,8 @@ interface Tab { ...@@ -22,8 +24,8 @@ interface Tab {
const TABS: Array<Tab> = [ const TABS: Array<Tab> = [
{ type: 'details', path: '', name: 'Details', component: <TxDetails/> }, { type: 'details', path: '', name: 'Details', component: <TxDetails/> },
{ type: 'internal_txn', path: '/internal-transactions', name: 'Internal txn' }, { type: 'internal_txn', path: '/internal-transactions', name: 'Internal txn', component: <TxInternals/> },
{ type: 'logs', path: '/logs', name: 'Logs' }, { type: 'logs', path: '/logs', name: 'Logs', component: <TxLogs/> },
{ type: 'state', path: '/state', name: 'State' }, { type: 'state', path: '/state', name: 'State' },
{ type: 'raw_trace', path: '/raw-trace', name: 'Raw trace' }, { type: 'raw_trace', path: '/raw-trace', name: 'Raw trace' },
]; ];
......
...@@ -78,10 +78,12 @@ const AddressWithDots = ({ address, fontWeight, truncated }: Props) => { ...@@ -78,10 +78,12 @@ const AddressWithDots = ({ address, fontWeight, truncated }: Props) => {
useEffect(() => { useEffect(() => {
if (!truncated) { if (!truncated) {
const resizeHandler = _debounce(calculateString, 50); const resizeHandler = _debounce(calculateString, 100);
window.addEventListener('resize', resizeHandler); const resizeObserver = new ResizeObserver(resizeHandler);
resizeObserver.observe(document.body);
return function cleanup() { return function cleanup() {
window.removeEventListener('resize', resizeHandler); resizeObserver.unobserve(document.body);
}; };
} }
}, [ calculateString, truncated ]); }, [ calculateString, truncated ]);
......
import { SearchIcon } from '@chakra-ui/icons';
import { Flex, Icon, Button, Circle, InputGroup, InputLeftElement, Input, useColorModeValue } from '@chakra-ui/react';
import type { ChangeEvent } from 'react';
import React from 'react';
import filterIcon from 'icons/filter.svg';
const FilterIcon = <Icon as={ filterIcon } boxSize={ 5 }/>;
const Filters = () => {
const [ isActive, setIsActive ] = React.useState(false);
const [ value, setValue ] = React.useState('');
const handleClick = React.useCallback(() => {
setIsActive(flag => !flag);
}, []);
const handleChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
}, []);
const badgeColor = useColorModeValue('white', 'black');
const badgeBgColor = useColorModeValue('blue.700', 'gray.50');
const searchIconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const inputBorderColor = useColorModeValue('blackAlpha.100', 'whiteAlpha.200');
return (
<Flex>
<Button
leftIcon={ FilterIcon }
rightIcon={ isActive ? <Circle bg={ badgeBgColor } size={ 5 } color={ badgeColor }>2</Circle> : undefined }
size="sm"
variant="outline"
colorScheme="gray-dark"
borderWidth="1px"
onClick={ handleClick }
isActive={ isActive }
px={ 1.5 }
>
Filter
</Button>
<InputGroup size="xs" ml={ 3 } maxW="360px">
<InputLeftElement ml={ 1 }>
<SearchIcon w={ 5 } h={ 5 } color={ searchIconColor }/>
</InputLeftElement>
<Input
paddingInlineStart="38px"
placeholder="Search by addresses, hash, method..."
ml="1px"
onChange={ handleChange }
borderColor={ inputBorderColor }
value={ value }
size="xs"
/>
</InputGroup>
</Flex>
);
};
export default Filters;
import { Box, Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txInternal';
import Filters from 'ui/shared/Filters';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
const TxInternals = () => {
return (
<Box>
<Filters/>
<TableContainer width="100%" mt={ 6 }>
<Table variant="simple" minWidth="950px">
<Thead>
<Tr>
<Th width="20%">Type</Th>
<Th width="calc(20% + 40px)" pr="0">From</Th>
<Th width="calc(20% - 40px)" pl="0">To</Th>
<Th width="20%" isNumeric>Value</Th>
<Th width="20%" isNumeric>Gas limit</Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<TxInternalsTableItem
key={ item.id }
{ ...item }
/>
)) }
</Tbody>
</Table>
</TableContainer>
</Box>
);
};
export default TxInternals;
import { Box } from '@chakra-ui/react';
import React from 'react';
import { data } from 'data/txLogs';
import TxLogItem from 'ui/tx/logs/TxLogItem';
const TxLogs = () => {
return (
<Box>
{ data.map((item, index) => <TxLogItem key={ index } { ...item } index={ index }/>) }
</Box>
);
};
export default TxLogs;
import { Tr, Td, Tag, Flex, Icon } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react';
import rightArrowIcon from 'icons/arrows/right.svg';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import TxStatus from 'ui/tx/TxStatus';
interface Props {
type: string;
status: 'success' | 'error';
from: string;
to: string;
value: number;
gasLimit: number;
}
const TxInternalTableItem = ({ type, status, from, to, value, gasLimit }: Props) => {
return (
<Tr alignItems="top">
<Td>
<Tag colorScheme="cyan" mr={ 2 }>{ capitalize(type) }</Tag>
<TxStatus status={ status }/>
</Td>
<Td pr="0">
<Flex alignItems="center">
<AddressIcon address={ from }/>
<AddressLinkWithTooltip address={ from } fontWeight="500" withCopy={ false } ml={ 2 }/>
<Icon as={ rightArrowIcon } boxSize={ 6 } mx={ 2 } color="gray.500"/>
</Flex>
</Td>
<Td pl="0">
<Flex alignItems="center">
<AddressIcon address={ to }/>
<AddressLinkWithTooltip address={ to } fontWeight="500" withCopy={ false } ml={ 2 }/>
</Flex>
</Td>
<Td isNumeric>
{ value }
</Td>
<Td isNumeric>
{ gasLimit.toLocaleString('en') }
</Td>
</Tr>
);
};
export default React.memo(TxInternalTableItem);
import { SearchIcon } from '@chakra-ui/icons';
import { Text, Grid, GridItem, Link, Tooltip, Button, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
import AddressIcon from 'ui/shared/AddressIcon';
import AddressLinkWithTooltip from 'ui/shared/AddressLinkWithTooltip';
import DecodedInputData from 'ui/shared/DecodedInputData';
import TxLogTopic from 'ui/tx/logs/TxLogTopic';
interface Props {
address: string;
topics: Array<{ hex: string }>;
data: string;
index: number;
}
const RowHeader = ({ children }: { children: React.ReactNode }) => <GridItem><Text fontWeight={ 500 }>{ children }</Text></GridItem>;
const TxLogItem = ({ address, index, topics, data }: Props) => {
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const dataBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
return (
<Grid gridTemplateColumns="200px 1fr" gap={ 8 } py={ 8 } _notFirst={{ borderTopWidth: '1px', borderTopColor: borderColor }}>
<RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center">
<AddressIcon address={ address }/>
<AddressLinkWithTooltip address={ address } columnGap={ 0 } ml={ 2 } fontWeight="400" withCopy={ false }/>
<Tooltip label="Find matches topic">
<Link ml={ 2 }>
<SearchIcon w={ 5 } h={ 5 }/>
</Link>
</Tooltip>
<Tooltip label="Log index">
<Button variant="outline" isActive ml="auto" size="sm" fontWeight={ 400 }>
{ index }
</Button>
</Tooltip>
</GridItem>
<RowHeader>Decode input data</RowHeader>
<GridItem>
<DecodedInputData/>
</GridItem>
<RowHeader>Topics</RowHeader>
<GridItem>
{ topics.map((item, index) => <TxLogTopic key={ index } { ...item } index={ index }/>) }
</GridItem>
<RowHeader>Data</RowHeader>
<GridItem p={ 4 } fontSize="sm" borderRadius="md" bgColor={ dataBgColor }>
{ data }
</GridItem>
</Grid>
);
};
export default React.memo(TxLogItem);
import { Flex, Button, Text, Select } from '@chakra-ui/react';
import React from 'react';
interface Props {
hex: string;
index: number;
}
type DataType = 'Hex' | 'Dec'
const OPTIONS: Array<DataType> = [ 'Hex', 'Dec' ];
const TxLogTopic = ({ hex, index }: Props) => {
const [ selectedDataType, setSelectedDataType ] = React.useState<DataType>('Hex');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedDataType(event.target.value as DataType);
}, []);
return (
<Flex alignItems="center" px={ 3 } _notFirst={{ mt: 3 }}>
<Button variant="outline" isActive size="xs" fontWeight={ 400 } mr={ 3 } w={ 6 }>
{ index }
</Button>
{ /* temporary condition juse to show different states of the component */ }
{ /* delete when ther will be real data */ }
{ index > 0 && (
<Select size="sm" borderRadius="base" value={ selectedDataType } onChange={ handleSelectChange } focusBorderColor="none" w="75px" mr={ 3 }>
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
) }
<Text>{ hex }</Text>
</Flex>
);
};
export default React.memo(TxLogTopic);
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