Commit f624bbc9 authored by tom's avatar tom

redesign

parent 58df71fe
export const data = [
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
miner: 'KuCoin Pool',
after: {
balance: '0.012192910371186045',
nonce: '4',
},
before: {
balance: '0.008350264867549483',
nonce: '5',
},
diff: '0.003842645503636562',
storage: [
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
before: '0x000000000000000000000000730bc43aac5a6cf94a72f69a42adfa114fe119b5',
after: '0x000000000000000000000000730bc43aac5a6cf94a72f69a42adfa114fe119b5',
},
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
before: '0x730bc43aac5a6cf94a72f69a42adfa114fe119b5',
after: '0x000000000000000000000000730bc43aac5a6cf94a72f69a42adfa114fe119b5',
},
],
},
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
miner: 'KuCoin Pool',
after: {
balance: '0.012192910371186045',
nonce: '4',
},
before: {
balance: '0.008350264867549483',
nonce: '5',
},
diff: '0.003842645503636562',
storage: [
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
before: '0x000000000000000000000000730bc43aac5a6cf94a72f69a42adfa114fe119b5',
after: '0x000000000000000000000000730bc43aac5a6cf94a72f69a42adfa114fe119b5',
},
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
before: '0x730bc43aac5a6cf94a72f69a42adfa114fe119b5',
after: '0x000000000000000000000000730bc43aac5a6cf94a72f69a42adfa114fe119b5',
},
],
},
{
address: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
miner: 'KuCoin Pool',
after: {
balance: '0.012192910371186045',
},
before: {
balance: '0.008350264867549483',
},
diff: '-0.003842645503636562',
},
];
export type TTxState = Array<TTxStateItem>;
export type TTxStateItem = {
address: string;
miner: string;
after: {
balance: string;
nonce?: string;
};
before: {
balance: string;
nonce?: string;
};
diff: string;
storage?: Array<TTxStateItemStorage>;
}
export type TTxStateItemStorage = {
address: string;
before: string;
after: string;
}
......@@ -15,3 +15,5 @@ export const YEAR = 365 * DAY;
export const Kb = 1_000;
export const Mb = 1_000 * Kb;
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
......@@ -2,9 +2,9 @@ import type { AddressParam } from './addressParams';
export type TokenType = 'ERC-20' | 'ERC-721' | 'ERC-1155';
export interface TokenInfo {
export interface TokenInfo<T extends TokenType = TokenType> {
address: string;
type: TokenType;
type: T;
symbol: string | null;
name: string | null;
decimals: string | null;
......
import type { AddressParam } from './addressParams';
import type { TokenInfo } from './token';
import type { TokenTotal } from './tokenTransfer';
import type { Erc1155TotalPayload, Erc721TotalPayload } from './tokenTransfer';
export type TxStateChange = (TxStateChangeCoin | TxStateChangeToken) & {
address: AddressParam;
token: TokenInfo | null;
is_miner: boolean;
balance_before: string | null;
balance_after: string | null;
......@@ -14,14 +13,32 @@ export type TxStateChange = (TxStateChangeCoin | TxStateChangeToken) & {
export interface TxStateChangeCoin {
type: 'coin';
change: string;
token: null;
}
export interface TxStateChangeToken {
export type TxStateChangeToken = TxStateChangeTokenErc20 | TxStateChangeTokenErc721 | TxStateChangeTokenErc1155;
type NftTokenChange<T> = {
direction: 'from' | 'to';
total: T;
}
export interface TxStateChangeTokenErc20 {
type: 'token';
token: TokenInfo<'ERC-20'>;
change: string;
}
export interface TxStateChangeTokenErc721 {
type: 'token';
token: TokenInfo<'ERC-721'>;
change: Array<NftTokenChange<Erc721TotalPayload>>;
}
export interface TxStateChangeTokenErc1155 {
type: 'token';
change: {
direction: 'from' | 'to';
total: TokenTotal;
};
token: TokenInfo<'ERC-1155'>;
change: Array<NftTokenChange<Erc1155TotalPayload | Array<Erc1155TotalPayload>>>;
}
export type TxStateChanges = Array<TxStateChange>;
import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, StatArrow, Stat } from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import { Grid, GridItem } from '@chakra-ui/react';
import React from 'react';
import type { TxStateChange } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
// import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile';
// import TxStateStorageItem from './TxStateStorageItem';
import { formatData } from './utils';
import { getStateElements } from './utils';
interface Props {
data: TxStateChange;
......@@ -21,84 +16,31 @@ interface Props {
const TxStateListItem = ({ data }: Props) => {
const hasStorageData = false;
const { balanceBefore, balanceAfter, difference, isIncrease } = formatData(data);
const { before, after, change, hint } = getStateElements(data);
return (
<ListItemMobile>
<AccordionItem isDisabled={ !hasStorageData } border={ 0 } w="100%" display="flex" flexDirection="column">
{ ({ isExpanded }) => (
<Address flexGrow={ 1 } w="100%">
<AddressIcon address={ data.address }/>
<AddressLink type="address" hash={ data.address.hash } ml={ 2 } truncation="constant" mr="auto"/>
{ hint }
</Address>
<Grid gridTemplateColumns="90px 1fr" columnGap={ 3 } rowGap={ 2 }>
{ before && (
<>
<Flex mb={ 6 }>
<AccordionButton
_hover={{ background: 'unset' }}
padding="0"
mr={ 5 }
w="auto"
>
<Button
variant="outline"
borderWidth="1px"
// button can't be inside button (AccordionButton)
as="div"
isActive={ isExpanded }
size="sm"
fontWeight={ 400 }
isDisabled={ !hasStorageData }
colorScheme="gray"
// AccordionButton has its own opacity rule when disabled
_disabled={{ opacity: 1 }}
>
{ data.storage?.length || '0' }
</Button>
<AccordionIcon color="blue.600" width="30px"/>
</AccordionButton>
<Address flexGrow={ 1 }>
<AddressIcon address={ data.address }/>
<AddressLink type="address" hash={ data.address.hash } ml={ 2 }/>
</Address>
</Flex>
{ /* { hasStorageData && (
<AccordionPanel fontWeight={ 500 } p={ 0 }>
{ data.storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
</AccordionPanel>
) } */ }
<Flex rowGap={ 2 } flexDir="column" fontSize="sm" whiteSpace="pre" fontWeight={ 500 }>
{ data.is_miner && (
<Box>
<Text as="span">{ capitalize(getNetworkValidatorTitle()) }</Text>
<span> -</span>
</Box>
) }
<Box>
<Text as="span">Before { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ balanceBefore }</Text>
</Box>
{ /* { typeof before.nonce !== 'undefined' && (
<Box>
<Text as="span">Nonce:</Text>
<Text as="span" fontWeight={ 600 }>{ nbsp }{ before.nonce }</Text>
</Box>
) } */ }
<Box>
<Text as="span">After { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ balanceAfter }</Text>
</Box>
{ /* { typeof after.nonce !== 'undefined' && (
<Box>
<Text as="span">Nonce:</Text>
<Text as="span" fontWeight={ 600 }>{ nbsp }{ after.nonce }</Text>
</Box>
) } */ }
<Text>State difference { appConfig.network.currency.symbol }</Text>
<Stat>
{ difference }
<StatArrow ml={ 2 } type={ isIncrease ? 'increase' : 'decrease' }/>
</Stat>
</Flex>
<GridItem fontWeight={ 500 }>Before</GridItem>
<GridItem>{ before }</GridItem>
</>
) }
</AccordionItem>
{ after && (
<>
<GridItem fontWeight={ 500 }>After</GridItem>
<GridItem>{ after }</GridItem>
</>
) }
<GridItem fontWeight={ 500 }>Change</GridItem>
<GridItem>{ change }</GridItem>
</Grid>
</ListItemMobile>
);
};
......
import {
Grid,
GridItem,
Select,
Box,
useColorModeValue,
} from '@chakra-ui/react';
import React from 'react';
import type { TTxStateItemStorage } from 'data/txState';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
const TxStateStorageItem = ({ storageItem }: {storageItem: TTxStateItemStorage}) => {
const gridData = [
{ name: 'Storage Address:', value: storageItem.address },
{ name: 'Before:', value: storageItem.before, select: true },
{ name: 'After:', value: storageItem.after, select: true },
];
const backgroundColor = useColorModeValue('white', 'gray.900');
const OPTIONS = [ 'Hex', 'Number', 'Text', 'Address' ];
return (
<Grid
gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }}
columnGap={ 3 }
rowGap={{ base: 2.5, lg: 4 }}
px={{ base: 3, lg: 6 }}
py={{ base: 3, lg: 4 }}
backgroundColor={ useColorModeValue('blackAlpha.50', 'whiteAlpha.100') }
borderRadius="12px"
mb={ 4 }
fontSize="sm"
>
{ gridData.map((item) => (
<React.Fragment key={ item.name }>
<GridItem
alignSelf="center"
fontWeight={ 600 }
textAlign={{ base: 'start', lg: 'end' }}
_notFirst={{ mt: { base: 0.5, lg: 0 } }}
>
{ item.name }
</GridItem>
<GridItem display="flex" flexDir="row" columnGap={ 3 } alignItems="center" >
{ item.select && (
<Select
size="xs"
borderRadius="base"
focusBorderColor="none"
display="inline-block"
w="auto"
flexShrink={ 0 }
background={ backgroundColor }
>
{ OPTIONS.map((option) => <option key={ option } value={ option }>{ option }</option>) }
</Select>
) }
<Box fontWeight={ 500 } whiteSpace="nowrap" overflow="hidden">
<HashStringShortenDynamic fontWeight="500" hash={ item.value }/>
</Box>
</GridItem>
</React.Fragment>
)) }
</Grid>
);
};
export default TxStateStorageItem;
......@@ -4,13 +4,10 @@ import {
Tr,
Th,
} from '@chakra-ui/react';
import capitalize from 'lodash/capitalize';
import React from 'react';
import type { TxStateChanges } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import { default as Thead } from 'ui/shared/TheadSticky';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
......@@ -20,15 +17,14 @@ interface Props {
const TxStateTable = ({ data }: Props) => {
return (
<Table variant="simple" minWidth="950px" size="sm" w="auto" mt={ 6 }>
<Table variant="simple" minWidth="1000px" size="sm" w="auto" mt={ 6 }>
<Thead top={ 0 }>
<Tr>
<Th width="92px">Storage</Th>
<Th width="140px"/>
<Th width="146px">Address</Th>
<Th width="120px">{ capitalize(getNetworkValidatorTitle()) }</Th>
<Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `After ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency.symbol }` }</Th>
<Th width="33%" isNumeric>Before</Th>
<Th width="33%" isNumeric>After</Th>
<Th width="33%" isNumeric>Change</Th>
</Tr>
</Thead>
<Tbody>
......@@ -38,4 +34,4 @@ const TxStateTable = ({ data }: Props) => {
);
};
export default TxStateTable;
export default React.memo(TxStateTable);
import {
AccordionItem,
AccordionButton,
// AccordionPanel,
AccordionIcon,
Box,
Tr,
Td,
Stat,
StatArrow,
// Portal,
Button,
} from '@chakra-ui/react';
import React, { useRef } from 'react';
import { Tr, Td } from '@chakra-ui/react';
import React from 'react';
import type { TxStateChange } from 'types/api/txStateChanges';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
// import { nbsp } from 'lib/html-entities';
// import TxStateStorageItem from './TxStateStorageItem';
import { formatData } from './utils';
import { getStateElements } from './utils';
interface Props {
data: TxStateChange;
}
const TxStateTableItem = ({ data }: Props) => {
const ref = useRef<HTMLTableDataCellElement>(null);
const hasStorageData = false;
const { balanceBefore, balanceAfter, difference, isIncrease } = formatData(data);
const { before, after, change, hint } = getStateElements(data);
return (
<>
<AccordionItem as="tr" isDisabled={ !hasStorageData } fontWeight={ 500 } border={ 0 }>
{ ({ isExpanded }) => (
<>
<Td border={ 0 }>
<AccordionButton
_hover={{ background: 'unset' }}
padding="0"
>
<Button
variant="outline"
borderWidth="1px"
// button can't be inside button (AccordionButton)
as="div"
isActive={ isExpanded }
size="sm"
fontWeight={ 400 }
isDisabled={ !hasStorageData }
colorScheme="gray"
// AccordionButton has its own opacity rule when disabled
_disabled={{ opacity: 1 }}
>
{ data.storage?.length || '0' }
</Button>
<AccordionIcon color="blue.600" width="30px"/>
</AccordionButton>
</Td>
<Td border={ 0 }>
<Address height="30px">
<AddressIcon address={ data.address }/>
<AddressLink type="address" hash={ data.address.hash } fontWeight="500" truncation="constant" ml={ 2 }/>
</Address>
</Td>
<Td border={ 0 } lineHeight="30px">
{ data.is_miner ? 'Validator' : '-' }
</Td>
<Td border={ 0 } isNumeric lineHeight="30px">
<Box>{ balanceBefore }</Box>
{ /* { typeof txStateItem.after.nonce !== 'undefined' && (
<Box justifyContent="end" display="inline-flex">Nonce: <Text fontWeight={ 600 }>{ nbsp + txStateItem.after.nonce }</Text></Box>
) } */ }
</Td>
<Td border={ 0 } isNumeric lineHeight="30px">{ balanceAfter }</Td>
<Td border={ 0 } isNumeric lineHeight="30px">
<Stat>
{ difference }
<StatArrow ml={ 2 } type={ isIncrease ? 'increase' : 'decrease' }/>
</Stat>
</Td>
{ /* { hasStorageData && (
<Portal containerRef={ ref }>
<AccordionPanel fontWeight={ 500 }>
{ data.storage?.map((storageItem, index) => <TxStateStorageItem key={ index } storageItem={ storageItem }/>) }
</AccordionPanel>
</Portal>
) } */ }
</>
) }
</AccordionItem>
<Tr><Td colSpan={ 6 } ref={ ref } padding={ 0 }></Td></Tr>
</>
<Tr>
<Td lineHeight="30px">
{ hint || '-' }
</Td>
<Td>
<Address height="30px">
<AddressIcon address={ data.address }/>
<AddressLink type="address" hash={ data.address.hash } alias={ data.address.name } fontWeight="500" truncation="constant" ml={ 2 }/>
</Address>
</Td>
<Td isNumeric lineHeight="30px">{ before }</Td>
<Td isNumeric lineHeight="30px">{ after }</Td>
<Td isNumeric lineHeight="30px"> { change } </Td>
</Tr>
);
};
export default TxStateTableItem;
export default React.memo(TxStateTableItem);
import BigNumber from 'bignumber.js';
import type { TxStateChange } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
export function formatData(data: TxStateChange) {
if (data.type === 'coin') {
const beforeBn = BigNumber(data.balance_before || '0').div(10 ** appConfig.network.currency.decimals);
const afterBn = BigNumber(data.balance_after || '0').div(10 ** appConfig.network.currency.decimals);
const differenceBn = afterBn.minus(beforeBn);
return {
balanceBefore: beforeBn.toFormat(),
balanceAfter: afterBn.toFormat(),
difference: differenceBn.toFormat(),
isIncrease: beforeBn.lte(afterBn),
};
}
const difference = Number(data.balance_after) - Number(data.balance_before);
return {
balanceBefore: data.balance_before,
balanceAfter: data.balance_after,
difference,
isIncrease: difference > 0,
};
}
import { Box, Flex, Stat, StatArrow, StatHelpText } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { TxStateChange } from 'types/api/txStateChanges';
import appConfig from 'configs/app/config';
import { ZERO_ADDRESS } from 'lib/consts';
import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import trimTokenSymbol from 'lib/token/trimTokenSymbol';
import AddressLink from 'ui/shared/address/AddressLink';
import Hint from 'ui/shared/Hint';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
export function getStateElements(data: TxStateChange) {
const hint = (() => {
if (data.is_miner) {
return (
<Flex align="center" columnGap={ 1 }>
<Hint label="A block producer who successfully included the block into the blockchain"/>
<Box color="text_secondary" textTransform="capitalize">{ getNetworkValidatorTitle() }</Box>
</Flex>
);
}
if (data.address.hash === ZERO_ADDRESS) {
return (
<Flex align="center" columnGap={ 1 }>
<Hint label="Address used in tokens mintings and burnings"/>
<Box color="text_secondary" whiteSpace="nowrap">Burn address</Box>
</Flex>
);
}
return null;
})();
switch (data.type) {
case 'coin': {
const beforeBn = BigNumber(data.balance_before || '0').div(10 ** appConfig.network.currency.decimals);
const afterBn = BigNumber(data.balance_after || '0').div(10 ** appConfig.network.currency.decimals);
const differenceBn = afterBn.minus(beforeBn);
return {
before: <Box>{ beforeBn.toFormat() } { appConfig.network.currency.symbol }</Box>,
after: <Box>{ afterBn.toFormat() } { appConfig.network.currency.symbol }</Box>,
change: (
<Stat>
{ differenceBn.toFormat() }
<StatArrow ml={ 2 } type={ beforeBn.lte(afterBn) ? 'increase' : 'decrease' }/>
</Stat>
),
hint,
};
}
case 'token': {
const tokenLink = <AddressLink type="token" hash={ data.token.address } alias={ trimTokenSymbol(data.token?.symbol || data.token.address) }/>;
const change = (() => {
const difference = Number(data.balance_after) - Number(data.balance_before);
switch (data.token.type) {
case 'ERC-20': {
return (
<Stat>
<StatHelpText display="flex" justifyContent={{ base: 'flex-start', lg: 'flex-end' }} alignItems="center" flexWrap="nowrap">
{ difference }{ nbsp }
{ tokenLink }
<StatArrow ml={ 2 } type={ difference > 0 ? 'increase' : 'decrease' }/>
</StatHelpText>
</Stat>
);
}
case 'ERC-721':
case 'ERC-1155': {
if (typeof data.change === 'string') {
return null;
}
return data.change.map((item, index) => {
if (Array.isArray(item.total)) {
return item.total.map((element, index) => {
return (
<Stat key={ index }>
<StatHelpText display="flex" justifyContent={{ base: 'flex-start', lg: 'flex-end' }} alignItems="center" flexWrap="nowrap">
<TokenTransferNft hash={ data.token.address } id={ element.token_id } w="auto"/>
<StatArrow ml={ 2 } type={ item.direction === 'to' ? 'increase' : 'decrease' }/>
</StatHelpText>
</Stat>
);
});
}
return (
<Stat key={ index }>
<StatHelpText display="flex" justifyContent={{ base: 'flex-start', lg: 'flex-end' }} alignItems="center" flexWrap="nowrap">
<TokenTransferNft hash={ data.token.address } id={ item.total.token_id } w="auto"/>
<StatArrow ml={ 2 } type={ item.direction === 'to' ? 'increase' : 'decrease' }/>
</StatHelpText>
</Stat>
);
});
}
}
})();
return {
before: data.balance_before ? (
<Flex whiteSpace="pre-wrap" justifyContent={{ base: 'flex-start', lg: 'flex-end' }}>
<span>{ Number(data.balance_before).toLocaleString() } </span>
{ tokenLink }
</Flex>
) : null,
after: data.balance_after ? (
<Flex whiteSpace="pre-wrap" justifyContent={{ base: 'flex-start', lg: 'flex-end' }}>
<span>{ Number(data.balance_after).toLocaleString() } </span>
{ tokenLink }
</Flex>
) : null,
change,
hint,
};
}
}
}
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