Commit 86fa1c52 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into network-customization

parents d0dec571 debe099e
...@@ -4,6 +4,7 @@ import { ...@@ -4,6 +4,7 @@ import {
HStack, HStack,
Icon, Icon,
Text, Text,
Grid,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -32,31 +33,28 @@ const LatestTxsItem = ({ tx }: Props) => { ...@@ -32,31 +33,28 @@ const LatestTxsItem = ({ tx }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
if (isMobile) {
return ( return (
<Box <Box
width="100%" width="100%"
borderTop="1px solid" borderTop="1px solid"
borderColor="divider" borderColor="divider"
py={ 4 } py={ 4 }
px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor: 'divider' }} _last={{ borderBottom: '1px solid', borderColor: 'divider' }}
> >
<Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}>
{ !isMobile && <Flex mr={ 3 }><TxAdditionalInfo tx={ tx }/></Flex> }
<Box width={{ base: '100%', lg: 'calc(50% - 32px)' }}>
<Flex justifyContent="space-between"> <Flex justifyContent="space-between">
<HStack> <HStack>
<TxType types={ tx.tx_types }/> <TxType types={ tx.tx_types }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined }/> <TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined }/>
</HStack> </HStack>
{ isMobile && <TxAdditionalInfo tx={ tx } isMobile/> } <TxAdditionalInfo tx={ tx } isMobile/>
</Flex> </Flex>
<Flex <Flex
mt={ 2 } mt={ 2 }
alignItems="center" alignItems="center"
width="100%" width="100%"
justifyContent={{ base: 'space-between', lg: 'start' }} justifyContent="space-between"
mb={{ base: 6, lg: 0 }} mb={ 6 }
> >
<Flex mr={ 3 }> <Flex mr={ 3 }>
<Icon <Icon
...@@ -76,9 +74,7 @@ const LatestTxsItem = ({ tx }: Props) => { ...@@ -76,9 +74,7 @@ const LatestTxsItem = ({ tx }: Props) => {
</Flex> </Flex>
{ tx.timestamp && <Text variant="secondary" fontWeight="400" fontSize="sm">{ timeAgo }</Text> } { tx.timestamp && <Text variant="secondary" fontWeight="400" fontSize="sm">{ timeAgo }</Text> }
</Flex> </Flex>
</Box> <Flex alignItems="center" mb={ 3 }>
<Box width={{ base: '100%', lg: '50%' }}>
<Flex alignItems="center" mb={ 3 } justifyContent={{ base: 'start', lg: 'end' }}>
<Address> <Address>
<AddressIcon address={ tx.from }/> <AddressIcon address={ tx.from }/>
<AddressLink <AddressLink
...@@ -112,18 +108,102 @@ const LatestTxsItem = ({ tx }: Props) => { ...@@ -112,18 +108,102 @@ const LatestTxsItem = ({ tx }: Props) => {
</Address> </Address>
) } ) }
</Flex> </Flex>
<Flex fontSize="sm" justifyContent="end" flexDirection={{ base: 'column', lg: 'row' }}> <Box mb={ 2 } fontSize="sm">
<Box mr={{ base: 0, lg: 3 }} mb={{ base: 2, lg: 0 }}>
<Text as="span">Value { appConfig.network.currency.symbol } </Text> <Text as="span">Value { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text> <Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
</Box> </Box>
<Box> <Box fontSize="sm">
<Text as="span">Fee { appConfig.network.currency.symbol } </Text> <Text as="span">Fee { appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text> <Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text>
</Box> </Box>
</Box>
);
}
return (
<Box
width="100%"
minW="700px"
borderTop="1px solid"
borderColor="divider"
p={ 4 }
_last={{ borderBottom: '1px solid', borderColor: 'divider' }}
>
<Grid width="100%" gridTemplateColumns="3fr 2fr 150px" gridGap={ 8 }>
<Flex overflow="hidden" w="100%">
<TxAdditionalInfo tx={ tx }/>
<Box ml={ 3 } w="calc(100% - 40px)">
<HStack>
<TxType types={ tx.tx_types }/>
<TxStatus status={ tx.status } errorText={ tx.status === 'error' ? tx.result : undefined }/>
</HStack>
<Flex
mt={ 2 }
alignItems="center"
>
<Icon
as={ transactionIcon }
boxSize="30px"
color="link"
display="inline"
mr={ 2 }
/>
<Address overflow="hidden" w="calc(100% - 130px)" maxW="calc(100% - 130px)" mr={ 2 }>
<AddressLink
hash={ tx.hash }
type="transaction"
fontWeight="700"
/>
</Address>
{ tx.timestamp && <Text variant="secondary" fontWeight="400" fontSize="sm">{ timeAgo }</Text> }
</Flex> </Flex>
</Box> </Box>
</Flex> </Flex>
<Grid alignItems="center" templateColumns="24px auto">
<Icon
as={ rightArrowIcon }
boxSize={ 6 }
color="gray.500"
transform="rotate(90deg)"
/>
<Box overflow="hidden" ml={ 1 }>
<Address mb={ 2 }>
<AddressIcon address={ tx.from }/>
<AddressLink
type="address"
hash={ tx.from.hash }
alias={ tx.from.name }
fontWeight="500"
ml={ 2 }
fontSize="sm"
/>
</Address>
{ dataTo && (
<Address>
<AddressIcon address={ dataTo }/>
<AddressLink
type="address"
hash={ dataTo.hash }
alias={ dataTo.name }
fontWeight="500"
ml={ 2 }
fontSize="sm"
/>
</Address>
) }
</Box>
</Grid>
<Box>
<Box mb={ 2 }>
<Text as="span" whiteSpace="pre">{ appConfig.network.currency.symbol } </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.value).dp(5).toFormat() }</Text>
</Box>
<Box>
<Text as="span">Fee </Text>
<Text as="span" variant="secondary">{ getValueWithUnit(tx.fee.value).dp(5).toFormat() }</Text>
</Box>
</Box>
</Grid>
</Box> </Box>
); );
}; };
......
...@@ -17,8 +17,7 @@ const LatestTxsItemSkeleton = () => { ...@@ -17,8 +17,7 @@ const LatestTxsItemSkeleton = () => {
px={{ base: 0, lg: 4 }} px={{ base: 0, lg: 4 }}
_last={{ borderBottom: '1px solid', borderColor: 'divider' }} _last={{ borderBottom: '1px solid', borderColor: 'divider' }}
> >
<Flex justifyContent="space-between" width="100%" alignItems="start" flexDirection={{ base: 'column', lg: 'row' }}> <Box width="100%" display={{ base: 'block', lg: 'none' }}>
<Box width="100%">
<HStack spacing={ 2 }> <HStack spacing={ 2 }>
<Skeleton w="101px" h="24px"/> <Skeleton w="101px" h="24px"/>
<Skeleton w="101px" h="24px"/> <Skeleton w="101px" h="24px"/>
...@@ -27,8 +26,8 @@ const LatestTxsItemSkeleton = () => { ...@@ -27,8 +26,8 @@ const LatestTxsItemSkeleton = () => {
mt={ 2 } mt={ 2 }
alignItems="center" alignItems="center"
width="100%" width="100%"
justifyContent={{ base: 'space-between', lg: 'start' }} justifyContent="space-between"
mb={{ base: 6, lg: 0 }} mb={ 6 }
> >
<Flex mr={ 3 } alignItems="center"> <Flex mr={ 3 } alignItems="center">
<Skeleton w="30px" h="30px" mr={ 2 }/> <Skeleton w="30px" h="30px" mr={ 2 }/>
...@@ -36,20 +35,45 @@ const LatestTxsItemSkeleton = () => { ...@@ -36,20 +35,45 @@ const LatestTxsItemSkeleton = () => {
</Flex> </Flex>
<Skeleton w="40px" h="12px"/> <Skeleton w="40px" h="12px"/>
</Flex> </Flex>
</Box>
<Box>
<Flex alignItems="center" mb={ 3 }> <Flex alignItems="center" mb={ 3 }>
<SkeletonCircle w="30px" h="30px" mr={ 2 }/> <SkeletonCircle w="30px" h="30px" mr={ 2 }/>
<Skeleton w="101px" h="12px" mr={ 5 }/> <Skeleton w="101px" h="12px" mr={ 5 }/>
<SkeletonCircle w="30px" h="30px" mr={ 2 }/> <SkeletonCircle w="30px" h="30px" mr={ 2 }/>
<Skeleton w="101px" h="12px"/> <Skeleton w="101px" h="12px"/>
</Flex> </Flex>
<Flex fontSize="sm" mt={ 3 } justifyContent="end" flexDirection={{ base: 'column', lg: 'row' }}> <Skeleton w="123px" h="12px" mb={ 2 } mt={ 3 }/>
<Skeleton w="123px" h="12px" mr={{ base: 0, lg: 9 }} mb={{ base: 2, lg: 0 }}/>
<Skeleton w="123px" h="12px"/> <Skeleton w="123px" h="12px"/>
</Box>
<Box display={{ base: 'none', lg: 'grid' }} width="100%" gridTemplateColumns="3fr 2fr 150px" gridGap={ 8 }>
<Flex w="100%">
<Skeleton w={ 5 } h={ 5 } mr={ 3 }/>
<Box w="100%">
<HStack>
<Skeleton w="101px" h="24px"/>
<Skeleton w="101px" h="24px"/>
</HStack>
<Flex alignItems="center" mt={ 2 }>
<Skeleton w="30px" h="30px" mr={ 2 }/>
<Skeleton w="calc(100% - 100px)" h="20px" mr={ 5 }/>
<Skeleton w="40px" h="16px"/>
</Flex> </Flex>
</Box> </Box>
</Flex> </Flex>
<Box>
<Flex alignItems="center" mb={ 2 } mt={ 1 }>
<SkeletonCircle w="24px" h="24px" mr={ 2 }/>
<Skeleton w="100%" h="16px"/>
</Flex>
<Flex alignItems="center">
<SkeletonCircle w="24px" h="24px" mr={ 2 }/>
<Skeleton w="100%" h="16px"/>
</Flex>
</Box>
<Box>
<Skeleton w="123px" h="16px" mb={ 4 } mt={ 2 }/>
<Skeleton w="123px" h="16px"/>
</Box>
</Box>
</Box> </Box>
); );
}; };
......
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect, devices } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import * as blockMock from 'mocks/blocks/block'; import * as blockMock from 'mocks/blocks/block';
...@@ -11,7 +11,7 @@ import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder'; ...@@ -11,7 +11,7 @@ import insertAdPlaceholder from 'playwright/utils/insertAdPlaceholder';
import Home from './Home'; import Home from './Home';
test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount, page }) => { test('default view -@default +@desktop-xl +@dark-mode', async({ mount, page }) => {
await page.route(buildApiUrl('homepage_stats'), (route) => route.fulfill({ await page.route(buildApiUrl('homepage_stats'), (route) => route.fulfill({
status: 200, status: 200,
body: JSON.stringify(statsMock.base), body: JSON.stringify(statsMock.base),
...@@ -46,3 +46,44 @@ test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount, ...@@ -46,3 +46,44 @@ test('default view -@default +@desktop-xl +@mobile +@dark-mode', async({ mount,
await expect(component.locator('main')).toHaveScreenshot(); await expect(component.locator('main')).toHaveScreenshot();
}); });
// had to separate mobile test, otherwise all the tests fell on CI
test.describe('mobile', () => {
test.use({ viewport: devices['iPhone 13 Pro'].viewport });
test('base view', async({ mount, page }) => {
await page.route(buildApiUrl('homepage_stats'), (route) => route.fulfill({
status: 200,
body: JSON.stringify(statsMock.base),
}));
await page.route(buildApiUrl('homepage_blocks'), (route) => route.fulfill({
status: 200,
body: JSON.stringify([
blockMock.base,
blockMock.base2,
]),
}));
await page.route(buildApiUrl('homepage_txs'), (route) => route.fulfill({
status: 200,
body: JSON.stringify([
txMock.base,
txMock.withContractCreation,
txMock.withTokenTransfer,
]),
}));
await page.route(buildApiUrl('homepage_chart_txs'), (route) => route.fulfill({
status: 200,
body: JSON.stringify(dailyTxsMock.base),
}));
const component = await mount(
<TestApp>
<Home/>
</TestApp>,
);
await insertAdPlaceholder(page);
await expect(component.locator('main')).toHaveScreenshot();
});
});
...@@ -47,7 +47,7 @@ const TxsTable = ({ ...@@ -47,7 +47,7 @@ const TxsTable = ({
<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: '132px', base: '66px' }}>From</Th> <Th width={{ xl: '132px', base: '66px' }}>From</Th>
<Th width={{ xl: currentAddress ? '48px' : '36px', base: '0' }}></Th> <Th width={{ xl: currentAddress ? '48px' : '36px', base: currentAddress ? '52px' : '28px' }}></Th>
<Th width={{ xl: '132px', base: '66px' }}>To</Th> <Th width={{ xl: '132px', base: '66px' }}>To</Th>
<Th width="20%" isNumeric> <Th width="20%" isNumeric>
<Link onClick={ sort('val') } display="flex" justifyContent="end"> <Link onClick={ sort('val') } display="flex" justifyContent="end">
......
import { import {
Box,
Tr, Tr,
Td, Td,
Tag, Tag,
...@@ -8,6 +7,7 @@ import { ...@@ -8,6 +7,7 @@ import {
Text, Text,
Show, Show,
Hide, Hide,
Flex,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
...@@ -44,14 +44,14 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement } ...@@ -44,14 +44,14 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement); const timeAgo = useTimeAgoIncrement(tx.timestamp, enableTimeIncrement);
const addressFrom = ( const addressFrom = (
<Address> <Address w="100%">
<AddressIcon address={ tx.from }/> <AddressIcon address={ tx.from }/>
<AddressLink type="address" hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isOut }/> <AddressLink type="address" hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isOut }/>
</Address> </Address>
); );
const addressTo = dataTo ? ( const addressTo = dataTo ? (
<Address> <Address w="100%">
<AddressIcon address={ dataTo }/> <AddressIcon address={ dataTo }/>
<AddressLink type="address" hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isIn }/> <AddressLink type="address" hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant" isDisabled={ isIn }/>
</Address> </Address>
...@@ -117,21 +117,23 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement } ...@@ -117,21 +117,23 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
</Show> </Show>
<Hide above="xl" ssr={ false }> <Hide above="xl" ssr={ false }>
<Td colSpan={ 3 }> <Td colSpan={ 3 }>
<Box> <Flex alignItems="center">
{ addressFrom }
{ (isIn || isOut) ? { (isIn || isOut) ?
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" my={ 2 }/> : ( <InOutTag isIn={ isIn } isOut={ isOut } width="48px"/> :
(
<Icon <Icon
as={ rightArrowIcon } as={ rightArrowIcon }
boxSize={ 6 } boxSize={ 6 }
mt={ 2 }
mb={ 1 }
color="gray.500" color="gray.500"
transform="rotate(90deg)" transform="rotate(90deg)"
/> />
) } )
}
<VStack alignItems="start" overflow="hidden" ml={ 1 }>
{ addressFrom }
{ addressTo } { addressTo }
</Box> </VStack>
</Flex>
</Td> </Td>
</Hide> </Hide>
<Td isNumeric> <Td isNumeric>
......
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