Commit fb280ee7 authored by tom goriunov's avatar tom goriunov Committed by GitHub

USD value for token transfers and more (#1360)

* show usd value in token transfers views

* txs table: collapse from/to into one column on md screens

* update tooltip text for daily txs chart

* update styles for "Relayed" OP withdrawal status

* update screenshots

* [skip ci] migrate from "ethereum-blockies-base64" to "blo"
parent b5363b2c
...@@ -25,7 +25,7 @@ export const erc20: TokenTransfer = { ...@@ -25,7 +25,7 @@ export const erc20: TokenTransfer = {
address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420', address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420',
circulating_market_cap: '117629601.61913824', circulating_market_cap: '117629601.61913824',
decimals: '18', decimals: '18',
exchange_rate: null, exchange_rate: '42',
holders: '46554', holders: '46554',
name: 'ARIANEE', name: 'ARIANEE',
symbol: 'ARIA', symbol: 'ARIA',
......
...@@ -124,6 +124,7 @@ export const withTokenTransfer: Transaction = { ...@@ -124,6 +124,7 @@ export const withTokenTransfer: Transaction = {
tokenTransferMock.erc1155C, tokenTransferMock.erc1155C,
tokenTransferMock.erc1155D, tokenTransferMock.erc1155D,
], ],
token_transfers_overflow: true,
tx_types: [ tx_types: [
'token_transfer', 'token_transfer',
], ],
......
...@@ -15,7 +15,7 @@ const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = { ...@@ -15,7 +15,7 @@ const dailyTxsIndicator: TChainIndicator<'homepage_chart_txs'> = {
title: 'Daily transactions', title: 'Daily transactions',
value: (stats) => Number(stats.transactions_today).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }), value: (stats) => Number(stats.transactions_today).toLocaleString(undefined, { maximumFractionDigits: 2, notation: 'compact' }),
icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>, icon: <Icon as={ txIcon } boxSize={ 6 } bgColor="#56ACD1" borderRadius="base" color="white"/>,
hint: `The total daily number of transactions on the blockchain for the last month.`, hint: `Number of transactions yesterday (0:00 - 23:59 UTC). The chart displays daily transactions for the past 30 days.`,
api: { api: {
resourceName: 'homepage_chart_txs', resourceName: 'homepage_chart_txs',
dataFn: (response) => ([ { dataFn: (response) => ([ {
......
import { Flex, Skeleton } from '@chakra-ui/react'; import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
import getCurrencyValue from 'lib/getCurrencyValue';
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';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
...@@ -37,15 +37,14 @@ const TokenTransferListItem = ({ ...@@ -37,15 +37,14 @@ const TokenTransferListItem = ({
enableTimeIncrement, enableTimeIncrement,
isLoading, isLoading,
}: Props) => { }: Props) => {
const value = (() => {
if (!('value' in total)) {
return null;
}
return BigNumber(total.value).div(BigNumber(10 ** Number(total.decimals))).dp(8).toFormat();
})();
const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement); const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement);
const { usd, valueStr } = 'value' in total ? getCurrencyValue({
value: total.value,
exchangeRate: token.exchange_rate,
accuracy: 8,
accuracyUsd: 2,
decimals: total.decimals || '0',
}) : { usd: null, valueStr: null };
const addressWidth = `calc((100% - ${ baseAddress ? '50px - 24px' : '24px - 24px' }) / 2)`; const addressWidth = `calc((100% - ${ baseAddress ? '50px - 24px' : '24px - 24px' }) / 2)`;
return ( return (
...@@ -112,10 +111,13 @@ const TokenTransferListItem = ({ ...@@ -112,10 +111,13 @@ const TokenTransferListItem = ({
width={ addressWidth } width={ addressWidth }
/> />
</Flex> </Flex>
{ value && ( { valueStr && (
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 } flexShrink={ 0 }>Value</Skeleton> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 } flexShrink={ 0 }>Value</Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"><span>{ value }</span></Skeleton> <Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ valueStr }</span>
{ usd && <span> (${ usd })</span> }
</Skeleton>
</Flex> </Flex>
) } ) }
</ListItemMobile> </ListItemMobile>
......
import { Tr, Td, Flex, Skeleton, Box } from '@chakra-ui/react'; import { Tr, Td, Flex, Skeleton, Box } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import getCurrencyValue from 'lib/getCurrencyValue';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement'; import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
...@@ -35,6 +35,13 @@ const TokenTransferTableItem = ({ ...@@ -35,6 +35,13 @@ const TokenTransferTableItem = ({
isLoading, isLoading,
}: Props) => { }: Props) => {
const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement); const timeAgo = useTimeAgoIncrement(timestamp, enableTimeIncrement);
const { usd, valueStr } = 'value' in total ? getCurrencyValue({
value: total.value,
exchangeRate: token.exchange_rate,
accuracy: 8,
accuracyUsd: 2,
decimals: total.decimals || '0',
}) : { usd: null, valueStr: null };
return ( return (
<Tr alignItems="top"> <Tr alignItems="top">
...@@ -111,9 +118,16 @@ const TokenTransferTableItem = ({ ...@@ -111,9 +118,16 @@ const TokenTransferTableItem = ({
/> />
</Td> </Td>
<Td isNumeric verticalAlign="top"> <Td isNumeric verticalAlign="top">
<Skeleton isLoaded={ !isLoading } display="inline-block" my="7px" wordBreak="break-all"> { valueStr && (
{ 'value' in total && BigNumber(total.value).div(BigNumber(10 ** Number(total.decimals))).dp(8).toFormat() } <Skeleton isLoaded={ !isLoading } display="inline-block" mt="7px" wordBreak="break-all">
</Skeleton> { valueStr }
</Skeleton>
) }
{ usd && (
<Skeleton isLoaded={ !isLoading } color="text_secondary" mt="10px" ml="auto" w="min-content">
<span>${ usd }</span>
</Skeleton>
) }
</Td> </Td>
</Tr> </Tr>
); );
......
...@@ -19,11 +19,11 @@ const Icon = dynamic( ...@@ -19,11 +19,11 @@ const Icon = dynamic(
} }
case 'blockie': { case 'blockie': {
const makeBlockie = (await import('ethereum-blockies-base64')).default; const { blo } = (await import('blo'));
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
return (props: IconProps) => { return (props: IconProps) => {
const data = makeBlockie(props.hash); const data = blo(props.hash as `0x${ string }`, props.size);
return ( return (
<Image <Image
src={ data } src={ data }
......
...@@ -36,13 +36,13 @@ const Icon = (props: IconProps) => { ...@@ -36,13 +36,13 @@ const Icon = (props: IconProps) => {
); );
}; };
type ContentProps = Omit<EntityBase.ContentBaseProps, 'text'> & Pick<EntityProps, 'hash'>; type ContentProps = Omit<EntityBase.ContentBaseProps, 'text'> & Pick<EntityProps, 'hash' | 'text'>;
const Content = chakra((props: ContentProps) => { const Content = chakra((props: ContentProps) => {
return ( return (
<EntityBase.Content <EntityBase.Content
{ ...props } { ...props }
text={ props.hash } text={ props.text ?? props.hash }
/> />
); );
}); });
...@@ -64,6 +64,7 @@ const Container = EntityBase.Container; ...@@ -64,6 +64,7 @@ const Container = EntityBase.Container;
export interface EntityProps extends EntityBase.EntityBaseProps { export interface EntityProps extends EntityBase.EntityBaseProps {
hash: string; hash: string;
text?: string;
} }
const TxEntity = (props: EntityProps) => { const TxEntity = (props: EntityProps) => {
......
import { Text, Icon, HStack } from '@chakra-ui/react'; import { Text, Icon, HStack } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Step } from './types';
import arrowIcon from 'icons/arrows/east.svg'; import arrowIcon from 'icons/arrows/east.svg';
import finalizedIcon from 'icons/finalized.svg'; import finalizedIcon from 'icons/finalized.svg';
import unfinalizedIcon from 'icons/unfinalized.svg'; import unfinalizedIcon from 'icons/unfinalized.svg';
type Props = { type Props = {
step: string; step: Step;
isLast: boolean; isLast: boolean;
isPassed: boolean; isPassed: boolean;
} }
...@@ -17,7 +19,7 @@ const VerificationStep = ({ step, isLast, isPassed }: Props) => { ...@@ -17,7 +19,7 @@ const VerificationStep = ({ step, isLast, isPassed }: Props) => {
return ( return (
<HStack gap={ 2 } color={ stepColor }> <HStack gap={ 2 } color={ stepColor }>
<Icon as={ isPassed ? finalizedIcon : unfinalizedIcon } boxSize={ 5 }/> <Icon as={ isPassed ? finalizedIcon : unfinalizedIcon } boxSize={ 5 }/>
<Text color={ stepColor }>{ step }</Text> <Text color={ stepColor }>{ typeof step === 'string' ? step : step.content }</Text>
{ !isLast && <Icon as={ arrowIcon } boxSize={ 5 }/> } { !isLast && <Icon as={ arrowIcon } boxSize={ 5 }/> }
</HStack> </HStack>
); );
......
...@@ -13,7 +13,7 @@ test('first step +@mobile +@dark-mode', async({ mount }) => { ...@@ -13,7 +13,7 @@ test('first step +@mobile +@dark-mode', async({ mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<Box p={ 10 }> <Box p={ 10 }>
<VerificationSteps step={ ZKEVM_L2_TX_STATUSES[0] } steps={ ZKEVM_L2_TX_STATUSES }/> <VerificationSteps currentStep={ ZKEVM_L2_TX_STATUSES[0] } steps={ ZKEVM_L2_TX_STATUSES }/>
</Box> </Box>
</TestApp>, </TestApp>,
); );
...@@ -25,7 +25,7 @@ test('second status', async({ mount }) => { ...@@ -25,7 +25,7 @@ test('second status', async({ mount }) => {
const component = await mount( const component = await mount(
<TestApp> <TestApp>
<VerificationSteps step={ ZKEVM_L2_TX_STATUSES[1] } steps={ ZKEVM_L2_TX_STATUSES }/> <VerificationSteps currentStep={ ZKEVM_L2_TX_STATUSES[1] } steps={ ZKEVM_L2_TX_STATUSES }/>
</TestApp>, </TestApp>,
); );
......
import { Skeleton, chakra } from '@chakra-ui/react'; import { Skeleton, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Step } from './types';
import VerificationStep from './VerificationStep'; import VerificationStep from './VerificationStep';
export interface Props<T extends string> { export interface Props {
step: T; currentStep: string;
steps: Array<T>; steps: Array<Step>;
isLoading?: boolean; isLoading?: boolean;
rightSlot?: React.ReactNode; rightSlot?: React.ReactNode;
className?: string; className?: string;
} }
const VerificationSteps = <T extends string>({ step, steps, isLoading, rightSlot, className }: Props<T>) => { const VerificationSteps = ({ currentStep, steps, isLoading, rightSlot, className }: Props) => {
const currentStepIndex = steps.indexOf(step); const currentStepIndex = steps.findIndex((step) => {
const label = typeof step === 'string' ? step : step.label;
return label === currentStep;
});
return ( return (
<Skeleton <Skeleton
...@@ -24,7 +29,12 @@ const VerificationSteps = <T extends string>({ step, steps, isLoading, rightSlot ...@@ -24,7 +29,12 @@ const VerificationSteps = <T extends string>({ step, steps, isLoading, rightSlot
flexWrap="wrap" flexWrap="wrap"
> >
{ steps.map((step, index) => ( { steps.map((step, index) => (
<VerificationStep step={ step } isLast={ index === steps.length - 1 && !rightSlot } isPassed={ index <= currentStepIndex } key={ step }/> <VerificationStep
key={ currentStep }
step={ step }
isLast={ index === steps.length - 1 && !rightSlot }
isPassed={ index <= currentStepIndex }
/>
)) } )) }
{ rightSlot } { rightSlot }
</Skeleton> </Skeleton>
......
export type Step = string | { label: string; content: React.ReactNode };
import { Flex, Skeleton } from '@chakra-ui/react'; import { Flex, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
import getCurrencyValue from 'lib/getCurrencyValue';
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';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
...@@ -27,15 +27,14 @@ const TokenTransferListItem = ({ ...@@ -27,15 +27,14 @@ const TokenTransferListItem = ({
tokenId, tokenId,
isLoading, isLoading,
}: Props) => { }: Props) => {
const value = (() => {
if (!('value' in total)) {
return null;
}
return BigNumber(total.value).div(BigNumber(10 ** Number(total.decimals))).dp(8).toFormat();
})();
const timeAgo = useTimeAgoIncrement(timestamp, true); const timeAgo = useTimeAgoIncrement(timestamp, true);
const { usd, valueStr } = 'value' in total ? getCurrencyValue({
value: total.value,
exchangeRate: token.exchange_rate,
accuracy: 8,
accuracyUsd: 2,
decimals: total.decimals || '0',
}) : { usd: null, valueStr: null };
return ( return (
<ListItemMobile rowGap={ 3 } isAnimated> <ListItemMobile rowGap={ 3 } isAnimated>
...@@ -72,15 +71,16 @@ const TokenTransferListItem = ({ ...@@ -72,15 +71,16 @@ const TokenTransferListItem = ({
fontWeight="500" fontWeight="500"
/> />
</Flex> </Flex>
{ value && (token.type === 'ERC-20' || token.type === 'ERC-1155') && ( { valueStr && (token.type === 'ERC-20' || token.type === 'ERC-1155') && (
<Flex columnGap={ 2 } w="100%"> <Flex columnGap={ 2 } w="100%">
<Skeleton isLoaded={ !isLoading } flexShrink={ 0 } fontWeight={ 500 }> <Skeleton isLoaded={ !isLoading } flexShrink={ 0 } fontWeight={ 500 }>
Value Value
</Skeleton> </Skeleton>
<Skeleton isLoaded={ !isLoading } color="text_secondary"> <Skeleton isLoaded={ !isLoading } color="text_secondary">
<span>{ value }</span> <span>{ valueStr }</span>
</Skeleton> </Skeleton>
{ token.symbol && <TruncatedValue isLoading={ isLoading } value={ token.symbol }/> } { token.symbol && <TruncatedValue isLoading={ isLoading } value={ token.symbol }/> }
{ usd && <Skeleton isLoaded={ !isLoading } color="text_secondary"><span>(${ usd })</span></Skeleton> }
</Flex> </Flex>
) } ) }
{ 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && ( { 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && (
......
import { Tr, Td, Grid, Skeleton, Box } from '@chakra-ui/react'; import { Tr, Td, Grid, Skeleton, Box } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import eastArrowIcon from 'icons/arrows/east.svg'; import eastArrowIcon from 'icons/arrows/east.svg';
import getCurrencyValue from 'lib/getCurrencyValue';
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';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
...@@ -26,6 +26,13 @@ const TokenTransferTableItem = ({ ...@@ -26,6 +26,13 @@ const TokenTransferTableItem = ({
isLoading, isLoading,
}: Props) => { }: Props) => {
const timeAgo = useTimeAgoIncrement(timestamp, true); const timeAgo = useTimeAgoIncrement(timestamp, true);
const { usd, valueStr } = 'value' in total ? getCurrencyValue({
value: total.value,
exchangeRate: token.exchange_rate,
accuracy: 8,
accuracyUsd: 2,
decimals: total.decimals || '0',
}) : { usd: null, valueStr: null };
return ( return (
<Tr alignItems="top"> <Tr alignItems="top">
...@@ -91,9 +98,16 @@ const TokenTransferTableItem = ({ ...@@ -91,9 +98,16 @@ const TokenTransferTableItem = ({
) } ) }
{ (token.type === 'ERC-20' || token.type === 'ERC-1155') && ( { (token.type === 'ERC-20' || token.type === 'ERC-1155') && (
<Td isNumeric verticalAlign="top"> <Td isNumeric verticalAlign="top">
<Skeleton isLoaded={ !isLoading } my="7px"> { valueStr && (
{ 'value' in total && BigNumber(total.value).div(BigNumber(10 ** Number(total.decimals))).dp(8).toFormat() } <Skeleton isLoaded={ !isLoading } display="inline-block" mt="7px" wordBreak="break-all">
</Skeleton> { valueStr }
</Skeleton>
) }
{ usd && (
<Skeleton isLoaded={ !isLoading } color="text_secondary" mt="10px" wordBreak="break-all">
<span>${ usd }</span>
</Skeleton>
) }
</Td> </Td>
) } ) }
</Tr> </Tr>
......
...@@ -167,7 +167,7 @@ const TxDetails = () => { ...@@ -167,7 +167,7 @@ const TxDetails = () => {
hint="Status of the transaction confirmation path to L1" hint="Status of the transaction confirmation path to L1"
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
> >
<VerificationSteps step={ data.zkevm_status } steps={ ZKEVM_L2_TX_STATUSES } isLoading={ isPlaceholderData }/> <VerificationSteps currentStep={ data.zkevm_status } steps={ ZKEVM_L2_TX_STATUSES } isLoading={ isPlaceholderData }/>
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ data.revert_reason && ( { data.revert_reason && (
...@@ -312,7 +312,7 @@ const TxDetails = () => { ...@@ -312,7 +312,7 @@ const TxDetails = () => {
<span>[ Contract creation ]</span> <span>[ Contract creation ]</span>
) } ) }
</DetailsInfoItem> </DetailsInfoItem>
{ data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash }/> } { data.token_transfers && <TxDetailsTokenTransfers data={ data.token_transfers } txHash={ data.hash } isOverflow={ data.token_transfers_overflow }/> }
<DetailsInfoItemDivider/> <DetailsInfoItemDivider/>
......
...@@ -14,6 +14,7 @@ import TxDetailsTokenTransfer from './TxDetailsTokenTransfer'; ...@@ -14,6 +14,7 @@ import TxDetailsTokenTransfer from './TxDetailsTokenTransfer';
interface Props { interface Props {
data: Array<TokenTransfer>; data: Array<TokenTransfer>;
txHash: string; txHash: string;
isOverflow: boolean;
} }
const TOKEN_TRANSFERS_TYPES = [ const TOKEN_TRANSFERS_TYPES = [
...@@ -22,16 +23,14 @@ const TOKEN_TRANSFERS_TYPES = [ ...@@ -22,16 +23,14 @@ const TOKEN_TRANSFERS_TYPES = [
{ title: 'Tokens burnt', hint: 'List of tokens burnt in the transaction', type: 'token_burning' }, { title: 'Tokens burnt', hint: 'List of tokens burnt in the transaction', type: 'token_burning' },
{ title: 'Tokens created', hint: 'List of tokens created in the transaction', type: 'token_spawning' }, { title: 'Tokens created', hint: 'List of tokens created in the transaction', type: 'token_spawning' },
]; ];
const VISIBLE_ITEMS_NUM = 3;
const TxDetailsTokenTransfers = ({ data, txHash }: Props) => { const TxDetailsTokenTransfers = ({ data, txHash, isOverflow }: Props) => {
const viewAllUrl = route({ pathname: '/tx/[hash]', query: { hash: txHash, tab: 'token_transfers' } }); const viewAllUrl = route({ pathname: '/tx/[hash]', query: { hash: txHash, tab: 'token_transfers' } });
const transferGroups = TOKEN_TRANSFERS_TYPES.map((group) => ({ const transferGroups = TOKEN_TRANSFERS_TYPES.map((group) => ({
...group, ...group,
items: data?.filter((token) => token.type === group.type) || [], items: data?.filter((token) => token.type === group.type) || [],
})); }));
const showViewAllLink = transferGroups.some(({ items }) => items.length > VISIBLE_ITEMS_NUM);
return ( return (
<> <>
...@@ -54,12 +53,12 @@ const TxDetailsTokenTransfers = ({ data, txHash }: Props) => { ...@@ -54,12 +53,12 @@ const TxDetailsTokenTransfers = ({ data, txHash }: Props) => {
w="100%" w="100%"
overflow="hidden" overflow="hidden"
> >
{ items.slice(0, VISIBLE_ITEMS_NUM).map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) } { items.map((item, index) => <TxDetailsTokenTransfer key={ index } data={ item }/>) }
</Flex> </Flex>
</DetailsInfoItem> </DetailsInfoItem>
); );
}) } }) }
{ showViewAllLink && ( { isOverflow && (
<> <>
<Show above="lg" ssr={ false }><GridItem></GridItem></Show> <Show above="lg" ssr={ false }><GridItem></GridItem></Show>
<GridItem fontSize="sm" alignItems="center" display="inline-flex" pl={{ base: '28px', lg: 0 }}> <GridItem fontSize="sm" alignItems="center" display="inline-flex" pl={{ base: '28px', lg: 0 }}>
......
import { test as base, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import type { L2WithdrawalStatus } from 'types/api/l2Withdrawals';
import contextWithEnvs from 'playwright/fixtures/contextWithEnvs';
import TestApp from 'playwright/TestApp';
import * as configs from 'playwright/utils/configs';
import TxDetailsWithdrawalStatus from './TxDetailsWithdrawalStatus';
const statuses: Array<L2WithdrawalStatus> = [
'Waiting for state root',
'Ready for relay',
'Relayed',
];
const test = base.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: contextWithEnvs(configs.featureEnvs.optimisticRollup) as any,
});
statuses.forEach((status) => {
test(`status="${ status }"`, async({ mount }) => {
const component = await mount(
<TestApp>
<TxDetailsWithdrawalStatus status={ status } l1TxHash="0x7d93a59a228e97d084a635181c3053e324237d07566ec12287eae6da2bcf9456"/>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
});
...@@ -25,30 +25,40 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => { ...@@ -25,30 +25,40 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
const hasClaimButton = status === 'Ready for relay'; const hasClaimButton = status === 'Ready for relay';
const steps = hasClaimButton ? WITHDRAWAL_STATUSES.slice(0, -1) : WITHDRAWAL_STATUSES; const steps = (() => {
switch (status) {
case 'Ready for relay':
return WITHDRAWAL_STATUSES.slice(0, -1);
case 'Relayed': {
if (l1TxHash) {
return WITHDRAWAL_STATUSES.map((status) => {
return status === 'Relayed' ? {
content: <TxEntityL1 hash={ l1TxHash } truncation="constant" text="Relayed" noIcon/>,
label: status,
} : status;
});
}
const rightSlot = (() => { return WITHDRAWAL_STATUSES;
if (status === 'Relayed' && l1TxHash) { }
return <TxEntityL1 hash={ l1TxHash } truncation="constant"/>;
}
if (hasClaimButton) { default:
return ( return WITHDRAWAL_STATUSES;
<Button
variant="outline"
size="sm"
as="a"
href="https://app.optimism.io/bridge/withdraw"
target="_blank"
>
Claim funds
</Button>
);
} }
return null;
})(); })();
const rightSlot = hasClaimButton ? (
<Button
variant="outline"
size="sm"
as="a"
href="https://app.optimism.io/bridge/withdraw"
target="_blank"
>
Claim funds
</Button>
) : null;
return ( return (
<DetailsInfoItem <DetailsInfoItem
title="Withdrawal status" title="Withdrawal status"
...@@ -56,7 +66,7 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => { ...@@ -56,7 +66,7 @@ const TxDetailsWithdrawalStatus = ({ status, l1TxHash }: Props) => {
> >
<VerificationSteps <VerificationSteps
steps={ steps as unknown as Array<L2WithdrawalStatus> } steps={ steps as unknown as Array<L2WithdrawalStatus> }
step={ status } currentStep={ status }
rightSlot={ rightSlot } rightSlot={ rightSlot }
my={ hasClaimButton ? '-6px' : 0 } my={ hasClaimButton ? '-6px' : 0 }
lineHeight={ hasClaimButton ? 8 : undefined } lineHeight={ hasClaimButton ? 8 : undefined }
......
import { Link, Table, Tbody, Tr, Th, Icon } from '@chakra-ui/react'; import { Link, Table, Tbody, Tr, Th, Icon, Show, Hide } from '@chakra-ui/react';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import React from 'react'; import React from 'react';
...@@ -48,9 +48,14 @@ const TxsTable = ({ ...@@ -48,9 +48,14 @@ const TxsTable = ({
<Th width="160px">Type</Th> <Th width="160px">Type</Th>
<Th width="20%">Method</Th> <Th width="20%">Method</Th>
{ showBlockInfo && <Th width="18%">Block</Th> } { showBlockInfo && <Th width="18%">Block</Th> }
<Th width={{ xl: '152px', base: '86px' }}>From</Th> <Th width={{ xl: '152px', base: '86px' }}>
<Show above="xl" ssr={ false }>From</Show>
<Hide above="xl" ssr={ false }>From / To</Hide>
</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' }}>
<Show above="xl" ssr={ false }>To</Show>
</Th>
{ !config.UI.views.tx.hiddenFields?.value && ( { !config.UI.views.tx.hiddenFields?.value && (
<Th width="20%" isNumeric> <Th width="20%" isNumeric>
<Link onClick={ sort('val') } display="flex" justifyContent="end"> <Link onClick={ sort('val') } display="flex" justifyContent="end">
......
...@@ -86,7 +86,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => { ...@@ -86,7 +86,7 @@ const ZkEvmL2TxnBatchDetails = ({ query }: Props) => {
title="Status" title="Status"
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
> >
<VerificationSteps steps={ ZKEVM_L2_TX_BATCH_STATUSES } step={ data.status } isLoading={ isPlaceholderData }/> <VerificationSteps steps={ ZKEVM_L2_TX_BATCH_STATUSES } currentStep={ data.status } isLoading={ isPlaceholderData }/>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Timestamp" title="Timestamp"
......
...@@ -7384,6 +7384,11 @@ bl@^4.0.3: ...@@ -7384,6 +7384,11 @@ bl@^4.0.3:
inherits "^2.0.4" inherits "^2.0.4"
readable-stream "^3.4.0" readable-stream "^3.4.0"
blo@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/blo/-/blo-1.1.1.tgz#ed781c5c516fba484ec8ec86105dc27f6c553209"
integrity sha512-1uGZInlRD4X1WQP2G1QjDGwGZ8HdGgFKqnzyRdA2TYYc0MOQCmCi37RTQ8oJuI0UF6DYFKXHwV/t1kZkO/fTaA==
bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0: bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70"
...@@ -9139,13 +9144,6 @@ eth-rpc-errors@^4.0.2: ...@@ -9139,13 +9144,6 @@ eth-rpc-errors@^4.0.2:
dependencies: dependencies:
fast-safe-stringify "^2.0.6" fast-safe-stringify "^2.0.6"
ethereum-blockies-base64@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ethereum-blockies-base64/-/ethereum-blockies-base64-1.0.2.tgz#4aebca52142bf4d16a3144e6e2b59303e39ed2b3"
integrity sha512-Vg2HTm7slcWNKaRhCUl/L3b4KrB8ohQXdd5Pu3OI897EcR6tVRvUqdTwAyx+dnmoDzj8e2bwBLDQ50ByFmcz6w==
dependencies:
pnglib "0.0.1"
ethereum-cryptography@^2.0.0: ethereum-cryptography@^2.0.0:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67" resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.1.2.tgz#18fa7108622e56481157a5cb7c01c0c6a672eb67"
...@@ -12180,11 +12178,6 @@ pngjs@^5.0.0: ...@@ -12180,11 +12178,6 @@ pngjs@^5.0.0:
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
pnglib@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/pnglib/-/pnglib-0.0.1.tgz#f9ab6f9c688f4a9d579ad8be28878a716e30c096"
integrity sha512-95ChzOoYLOPIyVmL+Y6X+abKGXUJlvOVLkB1QQkyXl7Uczc6FElUy/x01NS7r2GX6GRezloO/ecCX9h4U9KadA==
popmotion@11.0.3: popmotion@11.0.3:
version "11.0.3" version "11.0.3"
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9" resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9"
......
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