diff --git a/src/components/Blocks/Detail.tsx b/src/components/Blocks/Detail.tsx index b3f7dbc..5104fcc 100644 --- a/src/components/Blocks/Detail.tsx +++ b/src/components/Blocks/Detail.tsx @@ -95,6 +95,7 @@ const DetailsTab = ({ block }: { block: IChainBlockInfoResponse }) => { fontWeight={'normal'} h={0} fontSize={'md'} + to={generatePath(RoutePath.Validator, { address: proposer })} > {proposer} diff --git a/src/components/Layout/CopyButton.tsx b/src/components/Layout/CopyButton.tsx index be755a1..fb6e7d7 100644 --- a/src/components/Layout/CopyButton.tsx +++ b/src/components/Layout/CopyButton.tsx @@ -43,6 +43,12 @@ const withCopyLogic = (Component: typeof IconButton | typeof Button) => { export const CopyButton = withCopyLogic(Button) export const CopyButtonIcon = withCopyLogic(IconButton) +export type ReducedTextAndCopyProps = { + breakPoint?: Parameters[0] + to?: string + children?: string +} & ICopyButton + /** * 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. @@ -56,11 +62,7 @@ export const ReducedTextAndCopy = ({ to, children = '', ...rest -}: { - to?: string - breakPoint?: Parameters[0] - children?: string -} & ICopyButton) => { +}: ReducedTextAndCopyProps) => { 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) { diff --git a/src/components/Layout/IconLink.tsx b/src/components/Layout/IconLink.tsx new file mode 100644 index 0000000..9b1ebd7 --- /dev/null +++ b/src/components/Layout/IconLink.tsx @@ -0,0 +1,34 @@ +import { Flex, Icon, IconProps, Link } from '@chakra-ui/react' +import { generatePath, Link as RouterLink } from 'react-router-dom' +import { PropsWithChildren } from 'react' +import { RoutePath } from '~constants' +import { HiOutlineCube } from 'react-icons/hi2' +import { IconType } from 'react-icons' + +export const BlockIconLink = ({ height }: { height: number }) => ( + + {height} + +) + +type IconLinkProps = { + to: string + icon: IconType +} & IconProps + +export const IconLink = ({ to, icon, children, ...iconProps }: IconLinkProps & PropsWithChildren) => { + return ( + + + + {children} + + + ) +} diff --git a/src/components/Validators/Detail.tsx b/src/components/Validators/Detail.tsx new file mode 100644 index 0000000..6e8c17d --- /dev/null +++ b/src/components/Validators/Detail.tsx @@ -0,0 +1,118 @@ +import { Flex, Heading, HStack, Tab, TabList, TabPanel, TabPanels, Text, VStack } from '@chakra-ui/react' +import { ensure0x, IChainValidator } from '@vocdoni/sdk' +import { Trans, useTranslation } from 'react-i18next' +import { DetailsGrid, GridItemProps } from '~components/Layout/DetailsGrid' +import { QueryParamsTabs } from '~components/Layout/QueryParamsTabs' +import { RawContentBox } from '~components/Layout/ShowRawButton' +import { ReducedTextAndCopy } from '~components/Layout/CopyButton' +import { BlockIconLink } from '~components/Layout/IconLink' +import { ValidatorName } from '~components/Validators/ValidatorCard' +import { generatePath } from 'react-router-dom' +import { RoutePath } from '~constants' + +export type ValidatorFixedType = IChainValidator & { + // todo(kon): delete this type extension when https://github.com/vocdoni/vocdoni-sdk/pull/402 is merged + joinHeight: number + proposals: number + score: number + validatorAddress: string + votes: number +} + +const DetailsTab = ({ validator }: { validator: ValidatorFixedType }) => { + const address = ensure0x(validator.address) + const pubKey = ensure0x(validator.pubKey) + + const { t } = useTranslation() + + const details: GridItemProps[] = [ + { + label: t('validators.pubKey', { defaultValue: 'Public Key' }), + children: ( + + {pubKey} + + ), + }, + { + label: t('validators.account', { defaultValue: 'Account' }), + children: ( + + {address} + + ), + }, + { + label: t('validators.voting_power_cell', { defaultValue: 'Voting power' }), + children: validator.power, + }, + { + label: t('validators.score', { defaultValue: 'Score' }), + children: validator.score, + }, + { + label: t('validators.votes', { defaultValue: 'Votes' }), + children: validator.votes, + }, + ] + + return ( + + + + ) +} + +export const ValidatorDetail = ({ validator }: { validator: ValidatorFixedType }) => { + return ( + + + + Validator Details + + + + + Joined on block + + + + + + + + Details + + + Raw + + + + + + + + + + + + + ) +} diff --git a/src/components/Validators/ValidatorCard.tsx b/src/components/Validators/ValidatorCard.tsx index 1188183..f902dd6 100644 --- a/src/components/Validators/ValidatorCard.tsx +++ b/src/components/Validators/ValidatorCard.tsx @@ -1,40 +1,76 @@ -import { Card, CardBody, Flex, HStack, Text } from '@chakra-ui/react' -import { ensure0x, IChainValidator } from '@vocdoni/sdk' +import { Card, CardBody, Flex, HStack, Link, Text } from '@chakra-ui/react' import { Trans } from 'react-i18next' import { ReducedTextAndCopy } from '~components/Layout/CopyButton' +import { generatePath, Link as RouterLink } from 'react-router-dom' +import { RoutePath } from '~constants' +import { ValidatorFixedType } from '~components/Validators/Detail' -export const ValidatorCard = (validator: IChainValidator) => { +export const ValidatorName = ({ name, useCopy, address }: { name?: string; useCopy?: boolean; address: string }) => { + const showName = !!name + let addrsComponent = ( + + {address} + + ) + if (useCopy) { + addrsComponent = ( + + {address} + + ) + } + return ( + + {showName && ( + + {name} + + )} + {addrsComponent} + + ) +} + +export const ValidatorCard = (validator: ValidatorFixedType) => { return ( - - - - {ensure0x(validator.address)} - - - + + + + + + + + PubKey: + + + {validator.pubKey} + + - PubKey: + + Voting power: {{ power: validator.power }} + - - {validator.pubKey} - - - - - Voting power: {{ power: validator.power }} - - + - - + + ) } diff --git a/src/constants.ts b/src/constants.ts index 3320323..4b6507e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,6 +30,7 @@ export enum RoutePath { Transaction = '/transactions/:block/:index', TransactionsList = '/transactions/:page?', TransactionByHashOrHeight = '/transactions/:hashOrHeight', + Validator = '/validator/:address', Validators = '/validators', Verify = '/verify/:verifier?', } diff --git a/src/pages/validator.tsx b/src/pages/validator.tsx new file mode 100644 index 0000000..5ac56c0 --- /dev/null +++ b/src/pages/validator.tsx @@ -0,0 +1,19 @@ +import { useLoaderData, useParams } from 'react-router-dom' +import { ensure0x, IChainValidatorsListResponse } from '@vocdoni/sdk' +import { ValidatorDetail, ValidatorFixedType } from '~components/Validators/Detail' + +const Validator = () => { + const validators = (useLoaderData() as IChainValidatorsListResponse).validators as Array + + const { address }: { address?: string } = useParams() + + const validator = validators.find( + (v) => ensure0x(v.validatorAddress.toLowerCase()) === ensure0x(address?.toLowerCase() ?? '') + ) + + if (!address || !validator) throw new Error('Validator not found') + + return +} + +export default Validator diff --git a/src/pages/validators.tsx b/src/pages/validators.tsx index 216b2ae..ebf96e7 100644 --- a/src/pages/validators.tsx +++ b/src/pages/validators.tsx @@ -14,6 +14,8 @@ const Validators = () => { return ( {validators.map((validator, i) => ( + // todo(kon): remove this ignore when https://github.com/vocdoni/vocdoni-sdk/pull/402 is merged + // @ts-ignore ))} diff --git a/src/router/index.tsx b/src/router/index.tsx index 197c3ec..4662b60 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -21,6 +21,7 @@ const ProcessList = lazy(() => import('~pages/processes')) const Process = lazy(() => import('~pages/process')) const Transaction = lazy(() => import('~pages/transaction')) const TransactionsList = lazy(() => import('~pages/transactions')) +const Validator = lazy(() => import('~pages/validator')) const Validators = lazy(() => import('~pages/validators')) const Verify = lazy(() => import('~pages/verify')) @@ -145,6 +146,15 @@ export const RoutesProvider = () => { return await client.txInfoByBlock(Number(tx.blockHeight), Number(tx.transactionIndex)) }, }, + { + path: RoutePath.Validator, + element: ( + + + + ), + loader: async ({ params }) => await client.validatorsList(), + }, { path: RoutePath.Validators, element: (