Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tx page #53

Merged
merged 17 commits into from
Jul 10, 2024
153 changes: 65 additions & 88 deletions src/components/Blocks/Detail.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
import {
Box,
Flex,
Grid,
GridItem,
Heading,
IconButton,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
VStack,
} from '@chakra-ui/react'
import { IChainBlockInfoResponse } from '@vocdoni/sdk'
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 { PropsWithChildren, useEffect, useState } from 'react'
import { ResponsiveTextCopy } from '~components/Layout/CopyButton'
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 useQueryParams from '~src/router/use-query-params'

const DetailRow = ({ label, children }: { label: string } & PropsWithChildren) => {
return (
<>
<GridItem>
<Text fontWeight={'bold'}>{label}</Text>
</GridItem>
<GridItem>{children}</GridItem>
</>
)
}
import { DetailsGrid, GridItemProps } from '~components/Layout/DetailsGrid'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'
import { QueryParamsTabs } from '~components/Layout/QueryParamsTabs'

const HeightNavigator = ({ height }: { height: number }) => {
const { data, isLoading } = useBlocksHeight({
Expand Down Expand Up @@ -72,50 +47,63 @@ const HeightNavigator = ({ height }: { height: number }) => {
}

const DetailsTab = ({ block }: { block: IChainBlockInfoResponse }) => {
const proposer = block.header.proposerAddress
const proposer = ensure0x(block.header.proposerAddress)
const height = block.header.height
const hash = block.hash
const hash = ensure0x(block.hash)
const date = new Date(block.header.time)

const { t } = useTranslation()

const details: GridItemProps[] = [
{
label: t('blocks.height', { defaultValue: 'Height' }),
children: <HeightNavigator height={height} />,
},
{
label: t('blocks.timestamp', { defaultValue: 'Timestamp' }),
children: date.toString(),
},
{
label: t('blocks.transactions', { defaultValue: 'Transactions' }),
children: block.data.txs.length,
},
{
label: t('blocks.hash', { defaultValue: 'Hash' }),
children: (
<ReducedTextAndCopy
breakPoint={{ base: true, lg: false }}
p={0}
color={'textAccent1'}
toCopy={hash}
fontWeight={'normal'}
h={0}
fontSize={'md'}
>
{hash}
</ReducedTextAndCopy>
),
},
{
label: t('blocks.proposer', { defaultValue: 'Proposer' }),
children: (
<ReducedTextAndCopy
breakPoint={{ base: true, md: false }}
p={0}
color={'textAccent1'}
toCopy={proposer}
fontWeight={'normal'}
h={0}
fontSize={'md'}
>
{proposer}
</ReducedTextAndCopy>
),
},
]

return (
<VStack align='start'>
<Grid templateColumns={{ base: '1fr', sm: '1fr 4fr' }} gap={2} alignItems='start'>
<DetailRow label={t('blocks.height', { defaultValue: 'Height' })}>
<HeightNavigator height={height} />
</DetailRow>
<DetailRow label={t('blocks.timestamp', { defaultValue: 'Timestamp' })}>{date.toString()}</DetailRow>
<DetailRow label={t('blocks.transactions', { defaultValue: 'Transactions' })}>
{block.data.txs.length}
</DetailRow>
<DetailRow label={t('blocks.hash', { defaultValue: 'Hash' })}>
<ResponsiveTextCopy
breakPoint={{ base: true, lg: false }}
p={0}
color={'textAccent1'}
toCopy={hash}
fontWeight={'normal'}
h={0}
fontSize={'md'}
>
{hash}
</ResponsiveTextCopy>
</DetailRow>
<DetailRow label={t('blocks.proposer', { defaultValue: 'Proposer' })}>
<ResponsiveTextCopy
breakPoint={{ base: true, md: false }}
p={0}
color={'textAccent1'}
toCopy={proposer}
fontWeight={'normal'}
h={0}
fontSize={'md'}
>
{proposer}
</ResponsiveTextCopy>
</DetailRow>
</Grid>
<DetailsGrid details={details} />
</VStack>
)
}
Expand All @@ -124,17 +112,8 @@ export const BlockDetail = ({ block }: { block: IChainBlockInfoResponse }) => {
const height = block.header.height
const date = new Date(block.header.time)

const { queryParams, setQueryParams } = useQueryParams<{ tab: string }>()

const [tab, setTab] = useState(queryParams.tab ? parseInt(queryParams.tab) : 0)
const { formatDistance } = useDateFns()

// Ensure the correct tab is selected when browsing back/forward from the history
useEffect(() => {
const tabIndex = queryParams.tab ? parseInt(queryParams.tab) : 0
setTab(tabIndex)
}, [queryParams.tab])

return (
<Flex direction={'column'} mt={{ base: '20px', lg: '40px' }} gap={6} wordBreak='break-all'>
<VStack align='start'>
Expand All @@ -147,17 +126,15 @@ export const BlockDetail = ({ block }: { block: IChainBlockInfoResponse }) => {
{formatDistance(date, new Date())}
</Text>
</VStack>
<Tabs index={tab} onChange={(i) => setQueryParams({ tab: i.toString() })}>
<Box whiteSpace='nowrap' overflowX='auto'>
<TabList display='flex' flexWrap='wrap'>
<Tab>
<Trans i18nKey={'process.tab_details'}>Details</Trans>
</Tab>
<Tab>
<Trans i18nKey={'raw'}>Raw</Trans>
</Tab>
</TabList>
</Box>
<QueryParamsTabs>
<TabList display='flex' flexWrap='wrap'>
<Tab>
<Trans i18nKey={'process.tab_details'}>Details</Trans>
</Tab>
<Tab>
<Trans i18nKey={'raw'}>Raw</Trans>
</Tab>
</TabList>
<TabPanels>
<TabPanel>
<DetailsTab block={block} />
Expand All @@ -166,7 +143,7 @@ export const BlockDetail = ({ block }: { block: IChainBlockInfoResponse }) => {
<RawContentBox obj={block} />
</TabPanel>
</TabPanels>
</Tabs>
</QueryParamsTabs>
</Flex>
)
}
56 changes: 35 additions & 21 deletions src/components/Layout/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Button, ButtonProps, IconButton, Tooltip, useBreakpointValue, useClipboard } from '@chakra-ui/react'
import { Box, Button, ButtonProps, IconButton, Tooltip, useBreakpointValue, useClipboard } from '@chakra-ui/react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { IoCheckmark, IoCopy } from 'react-icons/io5'
import { shortHex } from '~utils/strings'
import { shortStr } from '~utils/strings'
import { Link as RouterLink } from 'react-router-dom'

type ICopyButton = ButtonProps & {
toCopy: string
Expand Down Expand Up @@ -42,30 +43,43 @@ const withCopyLogic = (Component: typeof IconButton | typeof Button) => {
export const CopyButton = withCopyLogic(Button)
export const CopyButtonIcon = withCopyLogic(IconButton)

export const ReducedTextAndCopy = ({ children, ...rest }: ICopyButton) => {
let text = children
if (typeof children === 'string' && children.length > 13) {
text = shortHex(children)
}
return (
<CopyButton fontWeight={'normal'} h={0} fontSize={'xs'} p={0} pt={1} {...rest}>
{text}
</CopyButton>
)
}

/**
* Children with copy button that is shows ReducedTextAndCopy on small screens
* It shows a text with a copy button.
* if the length of the string is more than 13 it cut the string to something like 6be21a...0000.
* If not breakpoint is defined it uses default one: { base: true, sm: false },
* @param breakPoint If it wants to be shown reduced on a specific breakpoint. If null it will show the entire text
* @param to if defined, the part of the text will be rendered as a link
* @param children The text to be shown
*/
export const ResponsiveTextCopy = ({
export const ReducedTextAndCopy = ({
breakPoint = { base: true, sm: false },
...props
to,
children = '',
...rest
}: {
to?: string
breakPoint?: Parameters<typeof useBreakpointValue>[0]
children?: string
} & ICopyButton) => {
const isSmallScreen = useBreakpointValue(breakPoint)
if (isSmallScreen) {
return <ReducedTextAndCopy {...props} />
let text = children
// If breakpoint is true and the length of the string is more than 13 it shorts the string
if (breakPoint && useBreakpointValue(breakPoint) && children.length > 13) {
text = shortStr(children)
}
return <CopyButton {...props} />

if (to) {
return (
<Box>
<Button as={RouterLink} to={to} variant={'text'} aria-label={children} {...rest} mr={0} pr={0}>
{text}
</Button>
<CopyButtonIcon {...rest} justifyContent={'start'} ml={2} pl={0} />
</Box>
)
}
return (
<CopyButton fontWeight={'normal'} h={0} fontSize={'xs'} p={0} pt={1} {...rest}>
{text}
</CopyButton>
)
}
32 changes: 32 additions & 0 deletions src/components/Layout/DetailsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Grid, GridItem, Text } from '@chakra-ui/react'
import { PropsWithChildren } from 'react'

export type GridItemProps = { label: string } & PropsWithChildren

/**
* Util component used to render a grid of details with its label
* @param details String label, and the component that should render on the grid
* @constructor
*/
export const DetailsGrid = ({ details }: { details: GridItemProps[] }) => {
return (
<Grid templateColumns={{ base: '1fr', sm: '1fr 4fr' }} gap={2} alignItems='start'>
{details.map(({ label, children }, key) => (
<DetailRow key={label} label={label}>
{children}
</DetailRow>
))}
</Grid>
)
}

const DetailRow = ({ label, children }: GridItemProps) => {
return (
<>
<GridItem>
<Text fontWeight={'bold'}>{label}</Text>
</GridItem>
<GridItem>{children}</GridItem>
</>
)
}
6 changes: 3 additions & 3 deletions src/components/Layout/ListPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ const ListPageLayout = ({
} & PropsWithChildren) => {
return (
<Flex direction='column' mt={10} gap={6}>
<Flex direction={{ base: 'column', md: 'row' }} justify='space-between' gap={4}>
<Flex direction='column'>
<Flex direction={{ base: 'column', md: 'row' }} justify='space-between' gap={4} align={'center'}>
<Flex direction='column' textAlign={{ base: 'center', md: 'start' }}>
<Heading isTruncated wordBreak='break-word'>
{title}
</Heading>
{subtitle && <Text color='lighterText'>{subtitle}</Text>}
</Flex>
{rightComponent && (
<Flex align='end' justify='end'>
<Flex align='center' justify={{ base: 'center', md: 'end' }}>
{rightComponent}
</Flex>
)}
Expand Down
21 changes: 21 additions & 0 deletions src/components/Layout/QueryParamsTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { TabsProps } from '@chakra-ui/tabs/dist/tabs'
import useQueryParams from '~src/router/use-query-params'
import { useEffect, useState } from 'react'
import { Tabs } from '@chakra-ui/react'

/**
* Reimplementation ob Tabs component to store the selected tab in the query params
* @param tabsProps
* @constructor
*/
export const QueryParamsTabs = (tabsProps: TabsProps) => {
const { queryParams, setQueryParams } = useQueryParams<{ tab: string }>()
const [tab, setTab] = useState(queryParams.tab ? parseInt(queryParams.tab) : 0)
// Ensure the correct tab is selected when browsing back/forward from the history
useEffect(() => {
const tabIndex = queryParams.tab ? parseInt(queryParams.tab) : 0
setTab(tabIndex)
}, [queryParams.tab])

return <Tabs index={tab} onChange={(i) => setQueryParams({ tab: i.toString() })} {...tabsProps} />
}
6 changes: 3 additions & 3 deletions src/components/Organizations/Detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AccountData, ensure0x, PublishedElection } from '@vocdoni/sdk'
import { Trans } from 'react-i18next'
import { FaUserAlt } from 'react-icons/fa'
import { useParams } from 'react-router-dom'
import { ResponsiveTextCopy } from '~components/Layout/CopyButton'
import { ReducedTextAndCopy } from '~components/Layout/CopyButton'
import { HeroHeaderLayout } from '~components/Layout/HeroHeaderLayout'
import { LoadingCards } from '~components/Layout/Loading'
import ShowRawButton from '~components/Layout/ShowRawButton'
Expand All @@ -29,9 +29,9 @@ const OrganizationDetail = () => {
<HeroHeaderLayout header={<OrganizationHeader fallbackSrc={FallbackHeaderImg} />}>
<VStack>
<OrganizationName fontSize='4xl' wordBreak='break-word' />
<ResponsiveTextCopy color={'textAccent1'} toCopy={id} fontWeight={'normal'} h={0} fontSize={'md'}>
<ReducedTextAndCopy color={'textAccent1'} toCopy={id} fontWeight={'normal'} h={0} fontSize={'md'}>
{id}
</ResponsiveTextCopy>
</ReducedTextAndCopy>
<Flex
as={'a'}
target='blank'
Expand Down
Loading
Loading