Commit 2b424192 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Transaction views customization (#1275)

* Stability UI customizations V0

Fixes #1269

* oops

* [skip ci] clean up
parent 09a4c9f1
import type { IdenticonType } from 'types/views/address'; import type { AddressViewId, IdenticonType } from 'types/views/address';
import { IDENTICON_TYPES } from 'types/views/address'; import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from 'types/views/address';
import { getEnvValue } from 'configs/app/utils'; import { getEnvValue, parseEnvJson } from 'configs/app/utils';
const identiconType: IdenticonType = (() => { const identiconType: IdenticonType = (() => {
const value = getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE'); const value = getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE');
...@@ -9,8 +9,24 @@ const identiconType: IdenticonType = (() => { ...@@ -9,8 +9,24 @@ const identiconType: IdenticonType = (() => {
return IDENTICON_TYPES.find((type) => value === type) || 'jazzicon'; return IDENTICON_TYPES.find((type) => value === type) || 'jazzicon';
})(); })();
const hiddenViews = (() => {
const parsedValue = parseEnvJson<Array<AddressViewId>>(getEnvValue('NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS')) || [];
if (!Array.isArray(parsedValue)) {
return undefined;
}
const result = ADDRESS_VIEWS_IDS.reduce((result, item) => {
result[item] = parsedValue.includes(item);
return result;
}, {} as Record<AddressViewId, boolean>);
return result;
})();
const config = Object.freeze({ const config = Object.freeze({
identiconType: identiconType, identiconType,
hiddenViews,
}); });
export default config; export default config;
export { default as block } from './block';
export { default as address } from './address'; export { default as address } from './address';
export { default as block } from './block';
export { default as tx } from './tx';
import type { TxAdditionalFieldsId, TxFieldsId } from 'types/views/tx';
import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from 'types/views/tx';
import { getEnvValue, parseEnvJson } from 'configs/app/utils';
const hiddenFields = (() => {
const parsedValue = parseEnvJson<Array<TxFieldsId>>(getEnvValue('NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS')) || [];
if (!Array.isArray(parsedValue)) {
return undefined;
}
const result = TX_FIELDS_IDS.reduce((result, item) => {
result[item] = parsedValue.includes(item);
return result;
}, {} as Record<TxFieldsId, boolean>);
return result;
})();
const additionalFields = (() => {
const parsedValue = parseEnvJson<Array<TxAdditionalFieldsId>>(getEnvValue('NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS')) || [];
if (!Array.isArray(parsedValue)) {
return undefined;
}
const result = TX_ADDITIONAL_FIELDS_IDS.reduce((result, item) => {
result[item] = parsedValue.includes(item);
return result;
}, {} as Record<TxAdditionalFieldsId, boolean>);
return result;
})();
const config = Object.freeze({
hiddenFields,
additionalFields,
});
export default config;
...@@ -32,6 +32,10 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c ...@@ -32,6 +32,10 @@ NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-c
## footer ## footer
## misc ## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}] NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
## views
# NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS=['top_accounts']
# NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee','gas_fees','burnt_fees']
# NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS=['fee_per_gas']
# app features # app features
NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_APP_ENV=development
......
...@@ -19,9 +19,12 @@ import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; ...@@ -19,9 +19,12 @@ import { SUPPORTED_WALLETS } from '../../../types/client/wallets';
import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks';
import type { ChainIndicatorId } from '../../../types/homepage'; import type { ChainIndicatorId } from '../../../types/homepage';
import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks'; import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwork, NETWORK_GROUPS } from '../../../types/networks';
import { IDENTICON_TYPES } from '../../../types/views/address'; import type { AddressViewId } from '../../../types/views/address';
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
import { BLOCK_FIELDS_IDS } from '../../../types/views/block'; import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
import type { BlockFieldId } from '../../../types/views/block'; import type { BlockFieldId } from '../../../types/views/block';
import type { TxAdditionalFieldsId, TxFieldsId } from '../../../types/views/tx';
import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx';
import { replaceQuotes } from '../../../configs/app/utils'; import { replaceQuotes } from '../../../configs/app/utils';
import * as regexp from '../../../lib/regexp'; import * as regexp from '../../../lib/regexp';
...@@ -360,6 +363,21 @@ const schema = yup ...@@ -360,6 +363,21 @@ const schema = yup
.json() .json()
.of(yup.string<BlockFieldId>().oneOf(BLOCK_FIELDS_IDS)), .of(yup.string<BlockFieldId>().oneOf(BLOCK_FIELDS_IDS)),
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: yup.string().oneOf(IDENTICON_TYPES), NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: yup.string().oneOf(IDENTICON_TYPES),
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS: yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string<AddressViewId>().oneOf(ADDRESS_VIEWS_IDS)),
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS: yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string<TxFieldsId>().oneOf(TX_FIELDS_IDS)),
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS: yup
.array()
.transform(replaceQuotes)
.json()
.of(yup.string<TxAdditionalFieldsId>().oneOf(TX_ADDITIONAL_FIELDS_IDS)),
// e. misc // e. misc
NEXT_PUBLIC_NETWORK_EXPLORERS: yup NEXT_PUBLIC_NETWORK_EXPLORERS: yup
......
...@@ -135,5 +135,11 @@ frontend: ...@@ -135,5 +135,11 @@ frontend:
_default: "['token_pocket','coinbase','metamask']" _default: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE:
_default: gradient_avatar _default: gradient_avatar
NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS:
_default: "['top_accounts']"
NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS:
_default: "['value','fee_currency','gas_price','gas_fees','burnt_fees']"
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS:
_default: "['fee_per_gas']"
NEXT_PUBLIC_USE_NEXT_JS_PROXY: NEXT_PUBLIC_USE_NEXT_JS_PROXY:
_default: true _default: true
\ No newline at end of file
...@@ -16,6 +16,8 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -16,6 +16,8 @@ The app instance could be customized by passing following variables to NodeJS en
- [Meta](ENVS.md#meta) - [Meta](ENVS.md#meta)
- [Views](ENVS.md#views) - [Views](ENVS.md#views)
- [Block](ENVS.md#block-views) - [Block](ENVS.md#block-views)
- [Address](ENVS.md#address-views)
- [Transaction](ENVS.md#transaction-views)
- [Misc](ENVS.md#misc) - [Misc](ENVS.md#misc)
- [App features](ENVS.md#app-features) - [App features](ENVS.md#app-features)
- [My account](ENVS.md#my-account) - [My account](ENVS.md#my-account)
...@@ -184,6 +186,36 @@ Settings for meta tags and OG tags ...@@ -184,6 +186,36 @@ Settings for meta tags and OG tags
| Variable | Type | Description | Compulsoriness | Default value | Example value | | Variable | Type | Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` | | NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE | `"github" \| "jazzicon" \| "gradient_avatar" \| "blockie"` | Style of address identicon appearance. Choose between [GitHub](https://github.blog/2013-08-14-identicons/), [Metamask Jazzicon](https://metamask.github.io/jazzicon/), [Gradient Avatar](https://github.com/varld/gradient-avatar) and [Ethereum Blocky](https://mycryptohq.github.io/ethereum-blockies-base64/) | - | `jazzicon` | `gradient_avatar` |
| NEXT_PUBLIC_VIEWS_ADDRESS_HIDDEN_VIEWS | `Array<AddressViewId>` | Address views that should not be displayed. See below the list of the possible id values. | - | - | `'["top_accounts"]'` |
##### Address views list
| Id | Description |
| --- | --- |
| `top_accounts` | Top accounts |
&nbsp;
#### Transaction views
| Variable | Type | Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS | `Array<TxFieldsId>` | Array of the transaction fields ids that should be hidden. See below the list of the possible id values. | - | - | `'["value","tx_fee"]'` |
| NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS | `Array<TxAdditionalFieldsId>` | Array of the additional fields ids that should be added to the transaction details. See below the list of the possible id values. | - | - | `'["fee_per_gas"]'` |
##### Transaction fields list
| Id | Description |
| --- | --- |
| `value` | Sent value |
| `fee_currency` | Fee currency |
| `gas_price` | Price per unit of gas |
| `tx_fee` | Total transaction fee |
| `gas_fees` | Gas fees breakdown |
| `burnt_fees` | Amount of native coin burnt for transaction |
##### Transaction additional fields list
| Id | Description |
| --- | --- |
| `fee_per_gas` | Amount of total fee divided by total amount of gas used by transaction |
&nbsp; &nbsp;
......
...@@ -49,12 +49,12 @@ export default function useNavItems(): ReturnType { ...@@ -49,12 +49,12 @@ export default function useNavItems(): ReturnType {
return React.useMemo(() => { return React.useMemo(() => {
let blockchainNavItems: Array<NavItem> | Array<Array<NavItem>> = []; let blockchainNavItems: Array<NavItem> | Array<Array<NavItem>> = [];
const topAccounts = { const topAccounts = !config.UI.views.address.hiddenViews?.top_accounts ? {
text: 'Top accounts', text: 'Top accounts',
nextRoute: { pathname: '/accounts' as const }, nextRoute: { pathname: '/accounts' as const },
icon: topAccountsIcon, icon: topAccountsIcon,
isActive: pathname === '/accounts', isActive: pathname === '/accounts',
}; } : null;
const blocks = { const blocks = {
text: 'Blocks', text: 'Blocks',
nextRoute: { pathname: '/blocks' as const }, nextRoute: { pathname: '/blocks' as const },
...@@ -90,7 +90,7 @@ export default function useNavItems(): ReturnType { ...@@ -90,7 +90,7 @@ export default function useNavItems(): ReturnType {
[ [
topAccounts, topAccounts,
verifiedContracts, verifiedContracts,
], ].filter(Boolean),
]; ];
} else { } else {
blockchainNavItems = [ blockchainNavItems = [
......
...@@ -113,3 +113,13 @@ export const suave: GetServerSideProps<Props> = async(context) => { ...@@ -113,3 +113,13 @@ export const suave: GetServerSideProps<Props> = async(context) => {
return base(context); return base(context);
}; };
export const accounts: GetServerSideProps<Props> = async(context) => {
if (config.UI.views.address.hiddenViews?.top_accounts) {
return {
notFound: true,
};
}
return base(context);
};
...@@ -16,4 +16,4 @@ const Page: NextPage = () => { ...@@ -16,4 +16,4 @@ const Page: NextPage = () => {
export default Page; export default Page;
export { base as getServerSideProps } from 'nextjs/getServerSideProps'; export { accounts as getServerSideProps } from 'nextjs/getServerSideProps';
...@@ -8,3 +8,9 @@ export const IDENTICON_TYPES = [ ...@@ -8,3 +8,9 @@ export const IDENTICON_TYPES = [
] as const; ] as const;
export type IdenticonType = ArrayElement<typeof IDENTICON_TYPES>; export type IdenticonType = ArrayElement<typeof IDENTICON_TYPES>;
export const ADDRESS_VIEWS_IDS = [
'top_accounts',
] as const;
export type AddressViewId = ArrayElement<typeof ADDRESS_VIEWS_IDS>;
import type { ArrayElement } from 'types/utils';
export const TX_FIELDS_IDS = [
'value',
'fee_currency',
'gas_price',
'tx_fee',
'gas_fees',
'burnt_fees',
] as const;
export type TxFieldsId = ArrayElement<typeof TX_FIELDS_IDS>;
export const TX_ADDITIONAL_FIELDS_IDS = [
'fee_per_gas',
] as const;
export type TxAdditionalFieldsId = ArrayElement<typeof TX_ADDITIONAL_FIELDS_IDS>;
...@@ -29,6 +29,7 @@ type Props = { ...@@ -29,6 +29,7 @@ type Props = {
const LatestTxsItem = ({ tx, isLoading }: Props) => { const LatestTxsItem = ({ tx, isLoading }: Props) => {
const dataTo = tx.to ? 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 columnNum = config.UI.views.tx.hiddenFields?.value && config.UI.views.tx.hiddenFields?.tx_fee ? 2 : 3;
return ( return (
<Box <Box
...@@ -40,7 +41,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -40,7 +41,7 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
_last={{ borderBottom: '1px solid', borderColor: 'divider' }} _last={{ borderBottom: '1px solid', borderColor: 'divider' }}
display={{ base: 'none', lg: 'block' }} display={{ base: 'none', lg: 'block' }}
> >
<Grid width="100%" gridTemplateColumns="3fr 2fr 150px" gridGap={ 8 }> <Grid width="100%" gridTemplateColumns={ columnNum === 2 ? '3fr 2fr' : '3fr 2fr 150px' } gridGap={ 8 }>
<Flex overflow="hidden" w="100%"> <Flex overflow="hidden" w="100%">
<TxAdditionalInfo tx={ tx } isLoading={ isLoading }/> <TxAdditionalInfo tx={ tx } isLoading={ isLoading }/>
<Box ml={ 3 } w="calc(100% - 40px)"> <Box ml={ 3 } w="calc(100% - 40px)">
...@@ -99,14 +100,18 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -99,14 +100,18 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
</Box> </Box>
</Grid> </Grid>
<Box> <Box>
<Skeleton isLoaded={ !isLoading } mb={ 2 }> { !config.UI.views.tx.hiddenFields?.value && (
<Text as="span" whiteSpace="pre">{ config.chain.currency.symbol } </Text> <Skeleton isLoaded={ !isLoading } mb={ 2 }>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text> <Text as="span" whiteSpace="pre">{ config.chain.currency.symbol } </Text>
</Skeleton> <Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
<Skeleton isLoaded={ !isLoading }> </Skeleton>
<Text as="span">Fee </Text> ) }
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text> { !config.UI.views.tx.hiddenFields?.tx_fee && (
</Skeleton> <Skeleton isLoaded={ !isLoading }>
<Text as="span">Fee </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text>
</Skeleton>
) }
</Box> </Box>
</Grid> </Grid>
</Box> </Box>
......
...@@ -89,14 +89,18 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => { ...@@ -89,14 +89,18 @@ const LatestTxsItem = ({ tx, isLoading }: Props) => {
/> />
) } ) }
</Flex> </Flex>
<Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content"> { !config.UI.views.tx.hiddenFields?.value && (
<Text as="span">Value { config.chain.currency.symbol } </Text> <Skeleton isLoaded={ !isLoading } mb={ 2 } fontSize="sm" w="fit-content">
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text> <Text as="span">Value { config.chain.currency.symbol } </Text>
</Skeleton> <Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
<Skeleton isLoaded={ !isLoading } fontSize="sm" w="fit-content"> </Skeleton>
<Text as="span">Fee { config.chain.currency.symbol } </Text> ) }
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text> { !config.UI.views.tx.hiddenFields?.tx_fee && (
</Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" w="fit-content">
<Text as="span">Fee { config.chain.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text>
</Skeleton>
) }
</Box> </Box>
); );
}; };
......
...@@ -45,6 +45,7 @@ import TextSeparator from 'ui/shared/TextSeparator'; ...@@ -45,6 +45,7 @@ import TextSeparator from 'ui/shared/TextSeparator';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
import Utilization from 'ui/shared/Utilization/Utilization'; import Utilization from 'ui/shared/Utilization/Utilization';
import TxDetailsActions from 'ui/tx/details/TxDetailsActions'; import TxDetailsActions from 'ui/tx/details/TxDetailsActions';
import TxDetailsFeePerGas from 'ui/tx/details/TxDetailsFeePerGas';
import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice'; import TxDetailsGasPrice from 'ui/tx/details/TxDetailsGasPrice';
import TxDetailsOther from 'ui/tx/details/TxDetailsOther'; import TxDetailsOther from 'ui/tx/details/TxDetailsOther';
import TxDetailsTokenTransfers from 'ui/tx/details/TxDetailsTokenTransfers'; import TxDetailsTokenTransfers from 'ui/tx/details/TxDetailsTokenTransfers';
...@@ -284,33 +285,41 @@ const TxDetails = () => { ...@@ -284,33 +285,41 @@ const TxDetails = () => {
<DetailsInfoItemDivider/> <DetailsInfoItemDivider/>
<DetailsInfoItem { !config.UI.views.tx.hiddenFields?.value && (
title="Value" <DetailsInfoItem
hint="Value sent in the native token (and USD) if applicable" title="Value"
isLoading={ isPlaceholderData } hint="Value sent in the native token (and USD) if applicable"
>
<CurrencyValue
value={ data.value }
currency={ config.chain.currency.symbol }
exchangeRate={ data.exchange_rate }
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
flexWrap="wrap" >
/> <CurrencyValue
</DetailsInfoItem> value={ data.value }
<DetailsInfoItem currency={ config.chain.currency.symbol }
title="Transaction fee" exchangeRate={ data.exchange_rate }
hint="Total transaction fee" isLoading={ isPlaceholderData }
isLoading={ isPlaceholderData } flexWrap="wrap"
> />
<CurrencyValue </DetailsInfoItem>
value={ data.fee.value } ) }
currency={ config.chain.currency.symbol } { !config.UI.views.tx.hiddenFields?.tx_fee && (
exchangeRate={ data.exchange_rate } <DetailsInfoItem
flexWrap="wrap" title="Transaction fee"
hint="Total transaction fee"
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> >
</DetailsInfoItem> <CurrencyValue
value={ data.fee.value }
currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : config.chain.currency.symbol }
exchangeRate={ data.exchange_rate }
flexWrap="wrap"
isLoading={ isPlaceholderData }
/>
</DetailsInfoItem>
) }
<TxDetailsGasPrice gasPrice={ data.gas_price } isLoading={ isPlaceholderData }/> <TxDetailsGasPrice gasPrice={ data.gas_price } isLoading={ isPlaceholderData }/>
<TxDetailsFeePerGas txFee={ data.fee.value } gasUsed={ data.gas_used } isLoading={ isPlaceholderData }/>
<DetailsInfoItem <DetailsInfoItem
title="Gas usage & limit by txn" title="Gas usage & limit by txn"
hint="Actual gas amount used by the transaction" hint="Actual gas amount used by the transaction"
...@@ -321,7 +330,8 @@ const TxDetails = () => { ...@@ -321,7 +330,8 @@ const TxDetails = () => {
<Skeleton isLoaded={ !isPlaceholderData }>{ BigNumber(data.gas_limit).toFormat() }</Skeleton> <Skeleton isLoaded={ !isPlaceholderData }>{ BigNumber(data.gas_limit).toFormat() }</Skeleton>
<Utilization ml={ 4 } value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() } isLoading={ isPlaceholderData }/> <Utilization ml={ 4 } value={ BigNumber(data.gas_used || 0).dividedBy(BigNumber(data.gas_limit)).toNumber() } isLoading={ isPlaceholderData }/>
</DetailsInfoItem> </DetailsInfoItem>
{ (data.base_fee_per_gas || data.max_fee_per_gas || data.max_priority_fee_per_gas) && ( { !config.UI.views.tx.hiddenFields?.gas_fees &&
(data.base_fee_per_gas || data.max_fee_per_gas || data.max_priority_fee_per_gas) && (
<DetailsInfoItem <DetailsInfoItem
title="Gas fees (Gwei)" title="Gas fees (Gwei)"
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
...@@ -354,7 +364,7 @@ const TxDetails = () => { ...@@ -354,7 +364,7 @@ const TxDetails = () => {
) } ) }
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ data.tx_burnt_fee && !config.features.rollup.isEnabled && ( { data.tx_burnt_fee && !config.UI.views.tx.hiddenFields?.burnt_fees && !config.features.rollup.isEnabled && (
<DetailsInfoItem <DetailsInfoItem
title="Burnt fees" title="Burnt fees"
hint={ `Amount of ${ config.chain.currency.symbol } burned for this transaction. Equals Block Base Fee per Gas * Gas Used` } hint={ `Amount of ${ config.chain.currency.symbol } burned for this transaction. Equals Block Base Fee per Gas * Gas Used` }
......
import { Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react';
import config from 'configs/app';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
interface Props {
txFee: string;
gasUsed: string | null;
isLoading?: boolean;
}
const TxDetailsFeePerGas = ({ txFee, gasUsed, isLoading }: Props) => {
if (!config.UI.views.tx.additionalFields?.fee_per_gas || !gasUsed) {
return null;
}
return (
<DetailsInfoItem
title="Fee per gas"
hint="Fee per gas"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } mr={ 1 }>
{ BigNumber(txFee).dividedBy(10 ** config.chain.currency.decimals).dividedBy(gasUsed).toFixed() }
{ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` }
</Skeleton>
</DetailsInfoItem>
);
};
export default TxDetailsFeePerGas;
...@@ -12,6 +12,10 @@ interface Props { ...@@ -12,6 +12,10 @@ interface Props {
} }
const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => { const TxDetailsGasPrice = ({ gasPrice, isLoading }: Props) => {
if (config.UI.views.tx.hiddenFields?.gas_price) {
return null;
}
return ( return (
<DetailsInfoItem <DetailsInfoItem
title="Gas price" title="Gas price"
......
...@@ -30,19 +30,21 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => { ...@@ -30,19 +30,21 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
return ( return (
<> <>
<Heading as="h4" size="sm" mb={ 6 }>Additional info </Heading> <Heading as="h4" size="sm" mb={ 6 }>Additional info </Heading>
<Box { ...sectionProps } mb={ 4 }> { !config.UI.views.tx.hiddenFields?.tx_fee && (
<Text { ...sectionTitleProps }>Transaction fee</Text> <Box { ...sectionProps } mb={ 4 }>
<Flex> <Text { ...sectionTitleProps }>Transaction fee</Text>
<CurrencyValue <Flex>
value={ tx.fee.value } <CurrencyValue
currency={ config.chain.currency.symbol } value={ tx.fee.value }
exchangeRate={ tx.exchange_rate } currency={ config.UI.views.tx.hiddenFields?.fee_currency ? '' : config.chain.currency.symbol }
accuracyUsd={ 2 } exchangeRate={ tx.exchange_rate }
flexWrap="wrap" accuracyUsd={ 2 }
rowGap={ 0 } flexWrap="wrap"
/> rowGap={ 0 }
</Flex> />
</Box> </Flex>
</Box>
) }
{ tx.gas_used !== null && ( { tx.gas_used !== null && (
<Box { ...sectionProps } mb={ 4 }> <Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Gas limit & usage by transaction</Text> <Text { ...sectionTitleProps }>Gas limit & usage by transaction</Text>
...@@ -54,7 +56,8 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => { ...@@ -54,7 +56,8 @@ const TxAdditionalInfoContent = ({ tx }: { tx: Transaction }) => {
</Flex> </Flex>
</Box> </Box>
) } ) }
{ (tx.base_fee_per_gas !== null || tx.max_fee_per_gas !== null || tx.max_priority_fee_per_gas !== null) && ( { !config.UI.views.tx.hiddenFields?.gas_fees &&
(tx.base_fee_per_gas !== null || tx.max_fee_per_gas !== null || tx.max_priority_fee_per_gas !== null) && (
<Box { ...sectionProps } mb={ 4 }> <Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Gas fees (Gwei)</Text> <Text { ...sectionTitleProps }>Gas fees (Gwei)</Text>
{ tx.base_fee_per_gas !== null && ( { tx.base_fee_per_gas !== null && (
......
...@@ -118,14 +118,20 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI ...@@ -118,14 +118,20 @@ const TxsListItem = ({ tx, isLoading, showBlockInfo, currentAddress, enableTimeI
/> />
) : '-' } ) : '-' }
</Flex> </Flex>
<Box mt={ 2 }> { !config.UI.views.tx.hiddenFields?.value && (
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Value { config.chain.currency.symbol } </Skeleton> <Box mt={ 2 }>
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary">{ getValueWithUnit(tx.value).toFormat() }</Skeleton> <Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Value { config.chain.currency.symbol } </Skeleton>
</Box> <Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary">{ getValueWithUnit(tx.value).toFormat() }</Skeleton>
<Box mt={ 2 } mb={ 3 }> </Box>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">Fee { config.chain.currency.symbol } </Skeleton> ) }
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary">{ getValueWithUnit(tx.fee.value).toFormat() }</Skeleton> { !config.UI.views.tx.hiddenFields?.tx_fee && (
</Box> <Box mt={ 2 } mb={ 3 }>
<Skeleton isLoaded={ !isLoading } display="inline-block" whiteSpace="pre">
Fee{ config.UI.views.tx.hiddenFields?.fee_currency ? ' ' : ` ${ config.chain.currency.symbol } ` }
</Skeleton>
<Skeleton isLoaded={ !isLoading } display="inline-block" variant="text_secondary">{ getValueWithUnit(tx.fee.value).toFormat() }</Skeleton>
</Box>
) }
</ListItemMobile> </ListItemMobile>
); );
}; };
......
...@@ -51,20 +51,24 @@ const TxsTable = ({ ...@@ -51,20 +51,24 @@ const TxsTable = ({
<Th width={{ xl: '152px', base: '86px' }}>From</Th> <Th width={{ xl: '152px', base: '86px' }}>From</Th>
<Th width={{ xl: currentAddress ? '48px' : '36px', base: currentAddress ? '52px' : '28px' }}></Th> <Th width={{ xl: currentAddress ? '48px' : '36px', base: currentAddress ? '52px' : '28px' }}></Th>
<Th width={{ xl: '152px', base: '86px' }}>To</Th> <Th width={{ xl: '152px', base: '86px' }}>To</Th>
<Th width="20%" isNumeric> { !config.UI.views.tx.hiddenFields?.value && (
<Link onClick={ sort('val') } display="flex" justifyContent="end"> <Th width="20%" isNumeric>
{ sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } <Link onClick={ sort('val') } display="flex" justifyContent="end">
{ sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } { sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ `Value ${ config.chain.currency.symbol }` } { sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
</Link> { `Value ${ config.chain.currency.symbol }` }
</Th> </Link>
<Th width="20%" isNumeric pr={ 5 }> </Th>
<Link onClick={ sort('fee') } display="flex" justifyContent="end"> ) }
{ sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } { !config.UI.views.tx.hiddenFields?.tx_fee && (
{ sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } <Th width="20%" isNumeric pr={ 5 }>
{ `Fee ${ config.chain.currency.symbol }` } <Link onClick={ sort('fee') } display="flex" justifyContent="end">
</Link> { sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
</Th> { sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Fee${ config.UI.views.tx.hiddenFields?.fee_currency ? '' : ` ${ config.chain.currency.symbol }` }` }
</Link>
</Th>
) }
</Tr> </Tr>
</TheadSticky> </TheadSticky>
<Tbody> <Tbody>
......
...@@ -13,6 +13,7 @@ import React from 'react'; ...@@ -13,6 +13,7 @@ import React from 'react';
import type { Transaction } from 'types/api/transaction'; import type { Transaction } from 'types/api/transaction';
import config from 'configs/app';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import Icon from 'ui/shared/chakra/Icon'; import Icon from 'ui/shared/chakra/Icon';
...@@ -153,12 +154,16 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement, ...@@ -153,12 +154,16 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement,
</Flex> </Flex>
</Td> </Td>
</Hide> </Hide>
<Td isNumeric> { !config.UI.views.tx.hiddenFields?.value && (
<CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/> <Td isNumeric>
</Td> <CurrencyValue value={ tx.value } accuracy={ 8 } isLoading={ isLoading }/>
<Td isNumeric> </Td>
<CurrencyValue value={ tx.fee.value } accuracy={ 8 } isLoading={ isLoading }/> ) }
</Td> { !config.UI.views.tx.hiddenFields?.tx_fee && (
<Td isNumeric>
<CurrencyValue value={ tx.fee.value } accuracy={ 8 } isLoading={ isLoading }/>
</Td>
) }
</Tr> </Tr>
); );
}; };
......
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