Skip to content

Commit

Permalink
Implement block txs list (#61)
Browse files Browse the repository at this point in the history
* Implement block txs list

* Show txns count on BlockCard.tsx

* Delete comments

* Fix rebase
  • Loading branch information
selankon authored Jul 17, 2024
1 parent a8af805 commit 923153c
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 24 deletions.
33 changes: 29 additions & 4 deletions src/components/Blocks/BlockCard.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
import { Box, Card, CardBody, Flex, Link, Text } from '@chakra-ui/react'
import { Box, Card, CardBody, Flex, Link, HStack, Icon, Text } from '@chakra-ui/react'
import { BlockError, BlockNotFoundError } from '@vocdoni/extended-sdk'
import { IChainBlockInfoResponse } from '@vocdoni/sdk'
import { Trans, useTranslation } from 'react-i18next'
import { BiTransferAlt } from 'react-icons/bi'

import { generatePath, Link as RouterLink } from 'react-router-dom'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'
import { RoutePath } from '~constants'
import { useDateFns } from '~i18n/use-date-fns'

export const BlockCard = ({ block }: { block: IChainBlockInfoResponse | BlockError }) => {
if (block instanceof BlockError) return <BlockErrorCard error={block} height={block.height} />
return <BlockInfoCard height={block.header.height} time={block.header.time} proposer={block.header.proposerAddress} />
return (
<BlockInfoCard
height={block.header.height}
time={block.header.time}
proposer={block.header.proposerAddress}
txn={block.data.txs.length}
/>
)
}

const BlockInfoCard = ({ height, time, proposer }: { height: number; time: string; proposer: string }) => {
const BlockInfoCard = ({
height,
time,
proposer,
txn,
}: {
height: number
time: string
proposer: string
txn: number
}) => {
const date = new Date(time)
const { formatDistance } = useDateFns()

return (
<Card>
<Link as={RouterLink} to={generatePath(RoutePath.Block, { height: height.toString() })}>
<Link as={RouterLink} to={generatePath(RoutePath.Block, { height: height.toString(), page: null })}>
<CardBody>
<Flex gap={1} direction={'column'}>
<Flex gap={3}>
<Text fontWeight='bold'># {height}</Text>
<HStack spacing={1}>
<Icon as={BiTransferAlt} boxSize={5} />
<Text fontSize={'sm'} fontWeight={'bold'}>
{txn}
</Text>
</HStack>
<Text fontWeight={100} color={'lighterText'}>
{formatDistance(date, new Date())}
</Text>
Expand Down
21 changes: 14 additions & 7 deletions src/components/Blocks/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Flex, Heading, IconButton, Tab, TabList, TabPanel, TabPanels, Text, VStack } from '@chakra-ui/react'
import { ensure0x, IChainBlockInfoResponse } from '@vocdoni/sdk'
import { Trans, useTranslation } from 'react-i18next'
import { RawContentBox } from '~components/Layout/ShowRawButton'
import { useDateFns } from '~i18n/use-date-fns'
import { GrNext, GrPrevious } from 'react-icons/gr'
import { RefreshIntervalBlocks, RoutePath } from '~constants'
import { useBlocksHeight } from '~queries/blocks'
import { generatePath, Link as RouterLink } from 'react-router-dom'
import { DetailsGrid, GridItemProps } from '~components/Layout/DetailsGrid'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'
import { DetailsGrid, GridItemProps } from '~components/Layout/DetailsGrid'
import { QueryParamsTabs } from '~components/Layout/QueryParamsTabs'
import { RawContentBox } from '~components/Layout/ShowRawButton'
import { BlockTransactionsList } from '~components/Transactions/TransactionList'
import { RefreshIntervalBlocks, RoutePath } from '~constants'
import { useDateFns } from '~i18n/use-date-fns'
import { useBlocksHeight } from '~queries/blocks'

const HeightNavigator = ({ height }: { height: number }) => {
const { data, isLoading } = useBlocksHeight({
Expand All @@ -27,7 +28,7 @@ const HeightNavigator = ({ height }: { height: number }) => {
{height >= 1 && (
<IconButton
as={RouterLink}
to={generatePath(RoutePath.Block, { height: (height - 1).toString() })}
to={generatePath(RoutePath.Block, { height: (height - 1).toString(), page: null })}
aria-label={t('blocks.previous_block')}
icon={<GrPrevious />}
size={'xs'}
Expand All @@ -36,7 +37,7 @@ const HeightNavigator = ({ height }: { height: number }) => {
{height < data && (
<IconButton
as={RouterLink}
to={generatePath(RoutePath.Block, { height: (height + 1).toString() })}
to={generatePath(RoutePath.Block, { height: (height + 1).toString(), page: null })}
aria-label={t('blocks.next_block')}
icon={<GrNext />}
size={'xs'}
Expand Down Expand Up @@ -131,6 +132,9 @@ export const BlockDetail = ({ block }: { block: IChainBlockInfoResponse }) => {
<Tab>
<Trans i18nKey={'process.tab_details'}>Details</Trans>
</Tab>
<Tab>
<Trans i18nKey={'process.tab_txs'}>Transactions</Trans>
</Tab>
<Tab>
<Trans i18nKey={'raw'}>Raw</Trans>
</Tab>
Expand All @@ -139,6 +143,9 @@ export const BlockDetail = ({ block }: { block: IChainBlockInfoResponse }) => {
<TabPanel>
<DetailsTab block={block} />
</TabPanel>
<TabPanel>
<BlockTransactionsList blockHeight={height} totalTxs={block.data.txs.length} />
</TabPanel>
<TabPanel>
<RawContentBox obj={block} />
</TabPanel>
Expand Down
7 changes: 5 additions & 2 deletions src/components/Envelope/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import voteImage from '/images/vocdoni-vote.png'
import { Flex, Heading, Image, Link, Text } from '@chakra-ui/react'
import { IVoteInfoResponse } from '@vocdoni/sdk'
import { formatDistance } from 'date-fns'
Expand All @@ -6,7 +7,6 @@ import { generatePath, Link as RouterLink } from 'react-router-dom'
import { CopyButton } from '~components/Layout/CopyButton'
import ShowRawButton from '~components/Layout/ShowRawButton'
import { RoutePath } from '~constants'
import voteImage from '/images/vocdoni-vote.png'

const EnvelopeDetail = (envelope: IVoteInfoResponse) => {
const { t } = useTranslation()
Expand Down Expand Up @@ -68,7 +68,10 @@ const EnvelopeDetail = (envelope: IVoteInfoResponse) => {
i18nKey={'envelopes.committed_in_block'}
components={{
a: (
<Link as={RouterLink} to={generatePath(RoutePath.Block, { height: envelope.blockHeight.toString() })} />
<Link
as={RouterLink}
to={generatePath(RoutePath.Block, { height: envelope.blockHeight.toString(), page: null })}
/>
),
}}
values={{ block: envelope.blockHeight }}
Expand Down
7 changes: 5 additions & 2 deletions src/components/Process/Detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import { Trans, useTranslation } from 'react-i18next'
import { BiEnvelope } from 'react-icons/bi'
import { generatePath, Link as RouterLink } from 'react-router-dom'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'
import { HeroHeaderLayout } from '~components/Layout/HeroHeaderLayout'
import { LoadingCards } from '~components/Layout/Loading'
import { RawContentBox } from '~components/Layout/ShowRawButton'
Expand All @@ -46,7 +47,6 @@ import InvalidElection from '~components/Process/InvalidElection'
import { FallbackHeaderImg, RoutePath } from '~constants'
import { useElectionKeys, useElectionVotesList } from '~queries/processes'
import { ucfirst } from '~utils/strings'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'

const Detail = () => {
const { election } = useElection()
Expand Down Expand Up @@ -279,7 +279,10 @@ const EnvelopeCard = ({ envelope, count }: { envelope: IElectionVote; count: num
</CardHeader>
<CardBody>
<Flex direction={'column'}>
<Text as={RouterLink} to={generatePath(RoutePath.Block, { height: envelope.blockHeight.toString() })}>
<Text
as={RouterLink}
to={generatePath(RoutePath.Block, { height: envelope.blockHeight.toString(), page: null })}
>
<Trans i18nKey={'envelope.block'} height={envelope.blockHeight}>
Block {{ height: envelope.blockHeight }}
</Trans>
Expand Down
55 changes: 53 additions & 2 deletions src/components/Transactions/TransactionList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Text } from '@chakra-ui/react'
import { keepPreviousData } from '@tanstack/react-query'
import { IBlockTransactionsResponse, IChainTxListResponse } from '@vocdoni/sdk'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import { generatePath, useNavigate, useParams } from 'react-router-dom'
import { PopoverInputSearch } from '~components/Layout/Inputs'
import { LoadingCards } from '~components/Layout/Loading'
Expand All @@ -9,6 +11,7 @@ import { RoutedPaginationProvider } from '~components/Pagination/PaginationProvi
import { RoutedPagination } from '~components/Pagination/RoutedPagination'
import { TransactionCard } from '~components/Transactions/TransactionCard'
import { PaginationItemsPerPage, RoutePath } from '~constants'
import { useBlockTransactions } from '~queries/blocks'
import { useTransactionList, useTransactionsCount } from '~queries/transactions'
import { retryUnlessNotFound } from '~utils/queries'

Expand Down Expand Up @@ -65,14 +68,62 @@ export const PaginatedTransactionList = () => {

const isLoading = isLoadingCount || isLoadingTx

return <TransactionsList isLoading={isLoading} data={data} isError={isError} error={error} totalPages={totalPages} />
}

/**
* Get transaction list by block height
* @constructor
*/
export const BlockTransactionsList = ({ blockHeight, totalTxs }: { blockHeight: number; totalTxs: number }) => {
const totalPages = Math.ceil(totalTxs / PaginationItemsPerPage)
const { page }: { page?: number } = useParams()
const currentPage = page && page > 0 ? Number(page - 1) : 0

const { data, isLoading, isError, error } = useBlockTransactions({
blockHeight,
page: currentPage,
placeholderData: keepPreviousData,
retry: retryUnlessNotFound,
enabled: totalTxs > 0,
})

if (totalTxs <= 0) {
return (
<Text>
<Trans i18nKey={'blocks.no_txs_on_block'}>There are no transactions.</Trans>
</Text>
)
}

return <TransactionsList isLoading={isLoading} data={data} isError={isError} error={error} totalPages={totalPages} />
}

const TransactionsList = ({
isLoading,
data,
isError,
error,
totalPages,
}: {
isLoading: boolean
data: IChainTxListResponse | IBlockTransactionsResponse | undefined
isError: boolean
error: Error | null
totalPages: number
}) => {
return (
<>
{isLoading && <LoadingCards />}
{!data || data?.transactions.length === 0 || (isError && <LoadingError error={error} />)}
{data && data.transactions.length > 0 && (
<RoutedPaginationProvider totalPages={totalPages} path={RoutePath.TransactionsList}>
{data.transactions.map((tx, i) => (
<TransactionCard key={i} {...tx} />
<TransactionCard
key={i}
{...tx}
blockHeight={(data as IBlockTransactionsResponse)?.blockNumber ?? tx.blockHeight} // If is IBlockTransactionsResponse the block height is not on tx info
/>
))}
<RoutedPagination />
</RoutedPaginationProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Transactions/TxDetails/TxDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const TxDetailsGrid = (tx: Tx) => {
children: (
<Text
as={RouterLink}
to={generatePath(RoutePath.Block, { height: blockHeight.toString() })}
to={generatePath(RoutePath.Block, { height: blockHeight.toString(), page: null })}
color={'textAccent1'}
>
{blockHeight}
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const PaginationItemsPerPage = 10
// route paths
export enum RoutePath {
Base = '/',
Block = '/block/:height',
Block = '/block/:height/:page?',
BlocksList = '/blocks/:page?',
Envelope = '/envelope/:verifier',
Organization = '/organization/:pid/:page?',
Expand Down
19 changes: 18 additions & 1 deletion src/queries/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { BlockError, ExtendedSDKClient } from '@vocdoni/extended-sdk'
import { useClient } from '@vocdoni/react-providers'
import { IChainBlockInfoResponse } from '@vocdoni/sdk'
import { IBlockTransactionsResponse, IChainBlockInfoResponse } from '@vocdoni/sdk'
import { useChainInfo, useChainInfoOptions } from '~queries/stats'

export const useBlockList = ({
Expand All @@ -26,3 +26,20 @@ export const useBlocksHeight = (options?: useChainInfoOptions) => {
const height = data?.height
return { data: height, ...rest }
}

export const useBlockTransactions = ({
blockHeight,
page,
...options
}: {
blockHeight: number
page: number
} & Omit<UseQueryOptions<IBlockTransactionsResponse>, 'queryKey'>) => {
const { client } = useClient<ExtendedSDKClient>()

return useQuery({
queryKey: ['blocks', 'list', 'transactions', blockHeight, page],
queryFn: () => client.blockTransactions(blockHeight, page),
...options,
})
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1977,10 +1977,10 @@
dependencies:
"@vocdoni/react-providers" "~0.4.4"

"@vocdoni/extended-sdk@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@vocdoni/extended-sdk/-/extended-sdk-0.0.3.tgz#d9dd2240493650cbac9c258a138bab91111a9a37"
integrity sha512-5izKqahI6b9qYY0nB4MzKwwkL/aIMNsdSoPfP71bHvM7YPuBw83uAe6ffv6KZLI2vUZK7zCKSScYlnklW+WTqg==
"@vocdoni/extended-sdk@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@vocdoni/extended-sdk/-/extended-sdk-0.0.4.tgz#5c53ae926619b6228c1ca24ba6cc370d48d3d772"
integrity sha512-quKdccVBilFuSy8WUPBNt1XYMPcPkxYpjeeDGS6yOKeKcb0FVT6U6H+gqfq5agZHBmtavpPWarfplJ4Ky1c6zA==

"@vocdoni/proto@1.15.8":
version "1.15.8"
Expand Down

2 comments on commit 923153c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.