Commit 97df2e3d authored by tom's avatar tom

skeletons for beacon withdrawals

parent 70260756
......@@ -47,7 +47,7 @@
},
{
"type": "shell",
"command": "NEXT_PUBLIC_API_HOST=${input:goerliApiHost} yarn dev:goerli",
"command": "NEXT_PUBLIC_API_HOST=${input:apiHost} yarn dev:goerli",
"problemMatcher": [],
"label": "dev server: goerli",
"detail": "start local dev server for Goerli network",
......@@ -68,7 +68,7 @@
},
{
"type": "shell",
"command": "NEXT_PUBLIC_API_HOST=${input:L2ApiHost} NEXT_PUBLIC_L1_BASE_URL=https://${input:goerliApiHost} yarn dev:goerli:optimism",
"command": "NEXT_PUBLIC_API_HOST=${input:L2ApiHost} NEXT_PUBLIC_L1_BASE_URL=https://${input:apiHost} yarn dev:goerli:optimism",
"problemMatcher": [],
"label": "dev server: goerli optimism",
"detail": "start local dev server for Goerli Optimism network",
......@@ -371,11 +371,12 @@
},
{
"type": "pickString",
"id": "goerliApiHost",
"id": "apiHost",
"description": "Choose API host:",
"options": [
"blockscout-main.test.aws-k8s.blockscout.com",
"eth-goerli.blockscout.com",
"eth.blockscout.com",
],
"default": ""
},
......
......@@ -21,6 +21,7 @@ NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://raw.githubusercontent.com/blockscout/
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_HAS_BEACON_CHAIN=false
# api config
NEXT_PUBLIC_API_HOST=blockscout-main.test.aws-k8s.blockscout.com
......
import type { NextPage } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Withdrawals from 'ui/pages/Withdrawals';
import Page from 'ui/shared/Page/Page';
const Withdrawals = dynamic(() => import('ui/pages/Withdrawals'), { ssr: false });
const WithdrawalsPage: NextPage = () => {
const title = getNetworkTitle();
......@@ -12,7 +15,9 @@ const WithdrawalsPage: NextPage = () => {
<Head>
<title>{ title }</title>
</Head>
<Withdrawals/>
<Page>
<Withdrawals/>
</Page>
</>
);
};
......
import type { WithdrawalsItem, WithdrawalsResponse } from 'types/api/withdrawals';
import { ADDRESS_PARAMS } from './addressParams';
export const WITHDRAWAL: WithdrawalsItem = {
amount: '12565723',
index: 3810697,
receiver: ADDRESS_PARAMS,
validator_index: 25987,
block_number: 9005713,
timestamp: '2023-05-12T19:29:12.000000Z',
};
export const WITHDRAWALS: WithdrawalsResponse = {
items: Array(50).fill(WITHDRAWAL),
next_page_params: {
index: 5,
items_count: 50,
},
};
......@@ -4,6 +4,7 @@ import React from 'react';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { WITHDRAWALS } from 'stubs/withdrawals';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
......@@ -15,24 +16,34 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
const hash = getQueryParamString(router.query.hash);
const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
const { data, isPlaceholderData, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_withdrawals',
pathParams: { hash },
scrollRef,
options: {
placeholderData: WITHDRAWALS,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
{ data.items.map((item) => <WithdrawalsListItem item={ item } key={ item.index } view="address"/>) }
{ data.items.map((item, index) => (
<WithdrawalsListItem
key={ item.index + Number(isPlaceholderData ? index : '') }
item={ item }
view="address"
isLoading={ isPlaceholderData }
/>
)) }
</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } view="address" top={ isPaginationVisible ? 80 : 0 }/>
<WithdrawalsTable items={ data.items } view="address" top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null ;
const actionBar = isPaginationVisible ? (
<ActionBar mt={ -6 } showShadow={ isLoading }>
<ActionBar mt={ -6 }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) : null;
......@@ -40,7 +51,7 @@ const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivE
return (
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
isLoading={ false }
items={ data?.items }
skeletonProps={{ isLongSkeleton: true, skeletonDesktopColumns: Array(5).fill(`${ 100 / 5 }%`), skeletonDesktopMinW: '950px' }}
emptyText="There are no withdrawals for this address."
......
......@@ -22,10 +22,22 @@ const BlockWithdrawals = ({ blockWithdrawalsQuery }: Props) => {
const content = blockWithdrawalsQuery.data?.items ? (
<>
<Show below="lg" ssr={ false }>
{ blockWithdrawalsQuery.data.items.map((item) => <WithdrawalsListItem item={ item } key={ item.index } view="block"/>) }
{ blockWithdrawalsQuery.data.items.map((item, index) => (
<WithdrawalsListItem
key={ item.index + (blockWithdrawalsQuery.isPlaceholderData ? String(index) : '') }
item={ item }
view="block"
isLoading={ blockWithdrawalsQuery.isPlaceholderData }
/>
)) }
</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ blockWithdrawalsQuery.data.items } view="block" top={ blockWithdrawalsQuery.isPaginationVisible ? 80 : 0 }/>
<WithdrawalsTable
items={ blockWithdrawalsQuery.data.items }
isLoading={ blockWithdrawalsQuery.isPlaceholderData }
top={ blockWithdrawalsQuery.isPaginationVisible ? 80 : 0 }
view="block"
/>
</Hide>
</>
) : null ;
......@@ -33,7 +45,7 @@ const BlockWithdrawals = ({ blockWithdrawalsQuery }: Props) => {
return (
<DataListDisplay
isError={ blockWithdrawalsQuery.isError }
isLoading={ blockWithdrawalsQuery.isLoading }
isLoading={ false }
items={ blockWithdrawalsQuery.data?.items }
skeletonProps={{ isLongSkeleton: true, skeletonDesktopColumns: Array(4).fill(`${ 100 / 4 }%`), skeletonDesktopMinW: '950px' }}
emptyText="There are no withdrawals for this block."
......
......@@ -11,6 +11,7 @@ import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import { BLOCK, BLOCK_TXS } from 'stubs/block';
import { WITHDRAWALS } from 'stubs/withdrawals';
import BlockDetails from 'ui/block/BlockDetails';
import BlockWithdrawals from 'ui/block/BlockWithdrawals';
import TextAd from 'ui/shared/ad/TextAd';
......@@ -56,6 +57,7 @@ const BlockPageContent = () => {
pathParams: { height },
options: {
enabled: Boolean(!blockQuery.isPlaceholderData && blockQuery.data?.height && appConfig.beaconChain.hasBeaconChain && tab === 'withdrawals'),
placeholderData: WITHDRAWALS,
},
});
......
......@@ -3,9 +3,9 @@ import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { WITHDRAWALS } from 'stubs/withdrawals';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem';
......@@ -14,14 +14,28 @@ import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable';
const Withdrawals = () => {
const isMobile = useIsMobile();
const { data, isError, isLoading, isPaginationVisible, pagination } = useQueryWithPages({
const { data, isError, isPlaceholderData, isPaginationVisible, pagination } = useQueryWithPages({
resourceName: 'withdrawals',
options: {
placeholderData: WITHDRAWALS,
},
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>{ data.items.map((item => <WithdrawalsListItem key={ item.index } item={ item } view="list"/>)) }</Show>
<Hide below="lg" ssr={ false }><WithdrawalsTable items={ data.items } view="list" top={ isPaginationVisible ? 80 : 0 }/></Hide>
<Show below="lg" ssr={ false }>
{ data.items.map(((item, index) => (
<WithdrawalsListItem
key={ item.index + (isPlaceholderData ? String(index) : '') }
item={ item }
view="list"
isLoading={ isPlaceholderData }
/>
))) }
</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } view="list" top={ isPaginationVisible ? 80 : 0 } isLoading={ isPlaceholderData }/>
</Hide>
</>
) : null;
......@@ -35,18 +49,18 @@ const Withdrawals = () => {
) : null;
return (
<Page>
<>
<PageTitle text="Withdrawals" withTextAd/>
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
isLoading={ false }
items={ data?.items }
skeletonProps={{ skeletonDesktopColumns: Array(6).fill(`${ 100 / 6 }%`), skeletonDesktopMinW: '950px' }}
emptyText="There are no withdrawals."
content={ content }
actionBar={ actionBar }
/>
</Page>
</>
);
};
......
......@@ -49,6 +49,7 @@ const Label = chakra(({ children, className, isLoading }: LabelProps) => {
fontWeight={ 500 }
lineHeight="20px"
my="5px"
justifySelf="start"
>
{ children }
</Skeleton>
......
......@@ -39,7 +39,7 @@ const TxState = () => {
return (
<>
<Text>
<Text mb={ 6 }>
A set of information that represents the current state is updated when a transaction takes place on the network.
The below is a summary of those changes.
</Text>
......
......@@ -12,7 +12,7 @@ interface Props {
const TxStateList = ({ data, isLoading }: Props) => {
return (
<Box mt={ 6 }>
<Box>
{ data.map((item, index) => <TxStateListItem key={ index } data={ item } isLoading={ isLoading }/>) }
</Box>
);
......
......@@ -18,7 +18,7 @@ interface Props {
const TxStateTable = ({ data, isLoading }: Props) => {
return (
<Table variant="simple" minWidth="1000px" size="sm" w="auto" mt={ 6 }>
<Table variant="simple" minWidth="1000px" size="sm" w="auto">
<Thead top={ 0 }>
<Tr>
<Th width="140px">Type</Th>
......
import { Icon } from '@chakra-ui/react';
import { Flex, Icon, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -16,7 +16,7 @@ import CurrencyValue from 'ui/shared/CurrencyValue';
import LinkInternal from 'ui/shared/LinkInternal';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
type Props = {
type Props = ({
item: WithdrawalsItem;
view: 'list';
} | {
......@@ -25,44 +25,52 @@ type Props = {
} | {
item: BlockWithdrawalsItem;
view: 'block';
};
}) & { isLoading?: boolean };
const WithdrawalsListItem = ({ item, view }: Props) => {
const WithdrawalsListItem = ({ item, isLoading, view }: Props) => {
return (
<ListItemMobileGrid.Container gridTemplateColumns="100px auto">
<ListItemMobileGrid.Label>Index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ item.index }
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.index }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label>Validator index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Validator index</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ item.validator_index }
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.validator_index }</Skeleton>
</ListItemMobileGrid.Value>
{ view !== 'block' && (
<>
<ListItemMobileGrid.Label>Block</ListItemMobileGrid.Label><ListItemMobileGrid.Value>
<LinkInternal
href={ route({ pathname: '/block/[height]', query: { height: item.block_number.toString() } }) }
display="flex"
width="fit-content"
alignItems="center"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 1 }/>
{ item.block_number }
</LinkInternal>
<ListItemMobileGrid.Label isLoading={ isLoading }>Block</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ isLoading ? (
<Flex columnGap={ 1 } alignItems="center">
<Skeleton boxSize={ 6 }/>
<Skeleton display="inline-block">{ item.block_number }</Skeleton>
</Flex>
) : (
<LinkInternal
href={ route({ pathname: '/block/[height]', query: { height: item.block_number.toString() } }) }
display="flex"
width="fit-content"
alignItems="center"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 1 }/>
{ item.block_number }
</LinkInternal>
) }
</ListItemMobileGrid.Value>
</>
) }
{ view !== 'address' && (
<>
<ListItemMobileGrid.Label>To</ListItemMobileGrid.Label><ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>To</ListItemMobileGrid.Label><ListItemMobileGrid.Value>
<Address>
<AddressIcon address={ item.receiver }/>
<AddressLink type="address" hash={ item.receiver.hash } truncation="dynamic" ml={ 2 }/>
<AddressIcon address={ item.receiver } isLoading={ isLoading }/>
<AddressLink type="address" hash={ item.receiver.hash } truncation="dynamic" ml={ 2 } isLoading={ isLoading }/>
</Address>
</ListItemMobileGrid.Value>
</>
......@@ -70,14 +78,14 @@ const WithdrawalsListItem = ({ item, view }: Props) => {
{ view !== 'block' && (
<>
<ListItemMobileGrid.Label>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Age</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
{ dayjs(item.timestamp).fromNow() }
<Skeleton isLoaded={ !isLoading } display="inline-block">{ dayjs(item.timestamp).fromNow() }</Skeleton>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label>Value</ListItemMobileGrid.Label>
<ListItemMobileGrid.Label isLoading={ isLoading }>Value</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<CurrencyValue value={ item.amount } currency={ appConfig.network.currency.symbol }/>
<CurrencyValue value={ item.amount } currency={ appConfig.network.currency.symbol } isLoading={ isLoading }/>
</ListItemMobileGrid.Value>
</>
) }
......
......@@ -12,6 +12,7 @@ import WithdrawalsTableItem from './WithdrawalsTableItem';
type Props = {
top: number;
isLoading?: boolean;
} & ({
items: Array<WithdrawalsItem>;
view: 'list';
......@@ -23,28 +24,28 @@ import WithdrawalsTableItem from './WithdrawalsTableItem';
view: 'block';
});
const WithdrawalsTable = ({ items, top, view = 'list' }: Props) => {
const WithdrawalsTable = ({ items, isLoading, top, view = 'list' }: Props) => {
return (
<Table variant="simple" size="sm" style={{ tableLayout: 'auto' }} minW="950px">
<Thead top={ top }>
<Tr>
<Th>Index</Th>
<Th>Validator index</Th>
{ view !== 'block' && <Th>Block</Th> }
{ view !== 'address' && <Th>To</Th> }
{ view !== 'block' && <Th>Age</Th> }
<Th>{ `Value ${ appConfig.network.currency.symbol }` }</Th>
<Th minW="140px">Index</Th>
<Th minW="200px">Validator index</Th>
{ view !== 'block' && <Th w="25%">Block</Th> }
{ view !== 'address' && <Th w="25%">To</Th> }
{ view !== 'block' && <Th w="25%">Age</Th> }
<Th w="25%">{ `Value ${ appConfig.network.currency.symbol }` }</Th>
</Tr>
</Thead>
<Tbody>
{ view === 'list' && (items as Array<WithdrawalsItem>).map((item) => (
<WithdrawalsTableItem key={ item.index } item={ item } view="list"/>
{ view === 'list' && (items as Array<WithdrawalsItem>).map((item, index) => (
<WithdrawalsTableItem key={ item.index + (isLoading ? String(index) : '') } item={ item } view="list" isLoading={ isLoading }/>
)) }
{ view === 'address' && (items as Array<AddressWithdrawalsItem>).map((item) => (
<WithdrawalsTableItem key={ item.index } item={ item } view="address"/>
{ view === 'address' && (items as Array<AddressWithdrawalsItem>).map((item, index) => (
<WithdrawalsTableItem key={ item.index + (isLoading ? String(index) : '') } item={ item } view="address" isLoading={ isLoading }/>
)) }
{ view === 'block' && (items as Array<BlockWithdrawalsItem>).map((item) => (
<WithdrawalsTableItem key={ item.index } item={ item } view="block"/>
{ view === 'block' && (items as Array<BlockWithdrawalsItem>).map((item, index) => (
<WithdrawalsTableItem key={ item.index + (isLoading ? String(index) : '') } item={ item } view="block" isLoading={ isLoading }/>
)) }
</Tbody>
</Table>
......
import { Td, Tr, Text, Icon } from '@chakra-ui/react';
import { Td, Tr, Icon, Skeleton, Flex } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react';
......@@ -14,7 +14,7 @@ import AddressLink from 'ui/shared/address/AddressLink';
import CurrencyValue from 'ui/shared/CurrencyValue';
import LinkInternal from 'ui/shared/LinkInternal';
type Props = {
type Props = ({
item: WithdrawalsItem;
view: 'list';
} | {
......@@ -23,45 +23,54 @@ import LinkInternal from 'ui/shared/LinkInternal';
} | {
item: BlockWithdrawalsItem;
view: 'block';
};
}) & { isLoading?: boolean };
const WithdrawalsTableItem = ({ item, view }: Props) => {
const WithdrawalsTableItem = ({ item, view, isLoading }: Props) => {
return (
<Tr>
<Td verticalAlign="middle">
<Text>{ item.index }</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.index }</Skeleton>
</Td>
<Td verticalAlign="middle">
<Text>{ item.validator_index }</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block">{ item.validator_index }</Skeleton>
</Td>
{ view !== 'block' && (
<Td verticalAlign="middle">
<LinkInternal
href={ route({ pathname: '/block/[height]', query: { height: item.block_number.toString() } }) }
display="flex"
width="fit-content"
alignItems="center"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 1 }/>
{ item.block_number }
</LinkInternal>
{ isLoading ? (
<Flex columnGap={ 1 } alignItems="center">
<Skeleton boxSize={ 6 }/>
<Skeleton display="inline-block">{ item.block_number }</Skeleton>
</Flex>
) : (
<LinkInternal
href={ route({ pathname: '/block/[height]', query: { height: item.block_number.toString() } }) }
display="flex"
width="fit-content"
alignItems="center"
>
<Icon as={ blockIcon } boxSize={ 6 } mr={ 1 }/>
{ item.block_number }
</LinkInternal>
) }
</Td>
) }
{ view !== 'address' && (
<Td verticalAlign="middle">
<Address>
<AddressIcon address={ item.receiver }/>
<AddressLink type="address" hash={ item.receiver.hash } truncation="constant" ml={ 2 }/>
<AddressIcon address={ item.receiver } isLoading={ isLoading }/>
<AddressLink type="address" hash={ item.receiver.hash } truncation="constant" ml={ 2 } isLoading={ isLoading }/>
</Address>
</Td>
) }
{ view !== 'block' && (
<Td verticalAlign="middle" pr={ 12 }>
<Text variant="secondary">{ dayjs(item.timestamp).fromNow() }</Text>
<Skeleton isLoaded={ !isLoading } display="inline-block" color="text_secondary">
<span>{ dayjs(item.timestamp).fromNow() }</span>
</Skeleton>
</Td>
) }
<Td verticalAlign="middle">
<CurrencyValue value={ item.amount }/>
<CurrencyValue value={ item.amount } isLoading={ isLoading }/>
</Td>
</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