diff --git a/CHANGELOG.md b/CHANGELOG.md index ca295768b..eae515c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#836](https://github.com/alleslabs/celatone-frontend/pull/836) Add recent 100 blocks chart - [#833](https://github.com/alleslabs/celatone-frontend/pull/833) Add link to transactions page to current bonded token component - [#832](https://github.com/alleslabs/celatone-frontend/pull/832) Add bonded token changes delegation related transactions - [#831](https://github.com/alleslabs/celatone-frontend/pull/831) Update zod schema to apply with new validator delegation related txs api spec diff --git a/src/lib/pages/validator-details/components/performance/PenaltySection.tsx b/src/lib/pages/validator-details/components/performance/PenaltySection.tsx index 766487309..3f229b1dc 100644 --- a/src/lib/pages/validator-details/components/performance/PenaltySection.tsx +++ b/src/lib/pages/validator-details/components/performance/PenaltySection.tsx @@ -1,5 +1,6 @@ import { Flex, Heading, Text } from "@chakra-ui/react"; +import { CustomIcon } from "lib/components/icon"; import type { ValidatorUptimeResponse } from "lib/services/validator"; import { PenaltyEvent } from "./PenaltyEvent"; @@ -25,7 +26,8 @@ export const PenaltySection = ({ penaltyEvents }: PenaltySectionProps) => ( Latest 90 Days - + + {penaltyEvents.length === 0 ? ( This validator never had any slash or jailed history within 90 days. diff --git a/src/lib/pages/validator-details/components/performance/RecentBlocksSection.tsx b/src/lib/pages/validator-details/components/performance/RecentBlocksSection.tsx index adb40f5fb..3b2660862 100644 --- a/src/lib/pages/validator-details/components/performance/RecentBlocksSection.tsx +++ b/src/lib/pages/validator-details/components/performance/RecentBlocksSection.tsx @@ -1,55 +1,160 @@ import { Box, Flex, Grid, Heading, Text } from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { forwardRef, useEffect, useRef } from "react"; -interface RecentBlocksSectionProps { - hasTitle?: boolean; +import { useNavContext } from "lib/app-provider"; +import { Loading } from "lib/components/Loading"; +import { ErrorFetching } from "lib/components/state"; +import { Tooltip } from "lib/components/Tooltip"; +import { useValidatorUptime } from "lib/services/validatorService"; +import { BlockVote } from "lib/types"; +import type { ValidatorAddr } from "lib/types"; +import { formatUTC } from "lib/utils"; + +interface BlockProps { + height: number; + vote: BlockVote; } -const Block = ({ isHighlighted = false }: { isHighlighted: boolean }) => ( - +const Block = forwardRef( + ({ height, vote }, ref) => { + let backgroundColor = "primary.main"; + let voteLabel = "Signed"; + + if (vote === BlockVote.PROPOSE) { + backgroundColor = "secondary.main"; + voteLabel = "Proposed"; + } else if (vote === BlockVote.ABSTAIN) { + backgroundColor = "error.dark"; + voteLabel = "Missed"; + } + + return ( + + + + {ref && ( + + )} + + + ); + } ); +interface RecentBlocksSectionProps { + validatorAddress: ValidatorAddr; +} + export const RecentBlocksSection = ({ - hasTitle = false, + validatorAddress, }: RecentBlocksSectionProps) => { - // TODO: remove mock up data - const blocks = new Array(100) - .fill(0) - .map((_, index) => ( - - )); + const { data, isLoading, dataUpdatedAt } = useValidatorUptime( + validatorAddress, + 100 + ); + const { isExpand } = useNavContext(); + const router = useRouter(); + + const parentRef = useRef(null); + const cursorRef = useRef(null); + const hoverTextRef = useRef(null); + + useEffect(() => { + const handleResize = () => { + const parentElement = parentRef.current; + const cursorElement = cursorRef.current; + const hoverTextElement = hoverTextRef.current; + + if (parentElement && cursorElement && hoverTextElement) { + const parentRect = parentElement.getBoundingClientRect(); + const cursorRect = cursorElement.getBoundingClientRect(); + const diffLeft = cursorRect.left - parentRect.left; + const diffRight = parentRect.right - cursorRect.right; + + if (diffLeft < 0 || diffRight < 0) return; + + if (diffLeft < parentRect.width / 2) { + hoverTextElement.style.left = `${diffLeft}px`; + hoverTextElement.style.right = "auto"; + } else { + hoverTextElement.style.right = `${diffRight}px`; + hoverTextElement.style.left = "auto"; + } + } + }; + + window.addEventListener("resize", handleResize); + + // Call the handler right away to set initial state + handleResize(); + + return () => window.removeEventListener("resize", handleResize); + }, [dataUpdatedAt, isExpand, router.asPath]); + + if (isLoading) return ; + if (!data) return ; return ( - - {hasTitle && ( - - - Most Recent 100 Blocks - - - Latest Update: timestamp - - - )} + + + + Most Recent 100 Blocks + + + Latest Update: {formatUTC(new Date(dataUpdatedAt))} + + - {blocks} + {data.recent100Blocks + .map((block, index) => ( + + )) + .reverse()} - {/* TODO: add arrow and align with last block */} - - Most Recent Blocks: 12345678 + + Most Recent Blocks: {data.recent100Blocks[0]?.height ?? "N/A"} ); diff --git a/src/lib/pages/validator-details/components/performance/UptimeSection.tsx b/src/lib/pages/validator-details/components/performance/UptimeSection.tsx index 3452066d6..86a4d69f6 100644 --- a/src/lib/pages/validator-details/components/performance/UptimeSection.tsx +++ b/src/lib/pages/validator-details/components/performance/UptimeSection.tsx @@ -15,7 +15,7 @@ import { useMobile } from "lib/app-provider"; import { CustomIcon } from "lib/components/icon"; import { ValueWithIcon } from "lib/components/ValueWithIcon"; import type { ValidatorUptimeResponse } from "lib/services/validator"; -import type { ComputedUptime, Ratio } from "lib/types"; +import type { ComputedUptime, Ratio, ValidatorAddr } from "lib/types"; import { formatRatio } from "lib/utils"; import { PenaltyEvent } from "./PenaltyEvent"; @@ -23,6 +23,7 @@ import { RecentBlocksLegends } from "./RecentBlocksLegends"; import { RecentBlocksSection } from "./RecentBlocksSection"; interface UptimeSectionProps { + validatorAddress: ValidatorAddr; uptimeData: ValidatorUptimeResponse; uptimeBlock: number; setUptimeBlock?: (block: number) => void; @@ -30,6 +31,7 @@ interface UptimeSectionProps { } export const UptimeSection = ({ + validatorAddress, uptimeData, uptimeBlock, setUptimeBlock, @@ -109,7 +111,9 @@ export const UptimeSection = ({ {onViewMore && ( <> - {isMobile && } + {isMobile && ( + + )} {uptimeData.events.length !== 0 && } {uptimeData.events.map((event) => ( diff --git a/src/lib/pages/validator-details/components/performance/index.tsx b/src/lib/pages/validator-details/components/performance/index.tsx index 9cb27c752..25b6a2830 100644 --- a/src/lib/pages/validator-details/components/performance/index.tsx +++ b/src/lib/pages/validator-details/components/performance/index.tsx @@ -34,6 +34,7 @@ export const Performance = ({ if (onViewMore) return ( setUptimeBlock(block)} @@ -60,7 +62,7 @@ export const Performance = ({ rounded={8} w="100%" > - + diff --git a/src/lib/pages/validator-details/components/tables/ProposedBlocksTable.tsx b/src/lib/pages/validator-details/components/tables/ProposedBlocksTable.tsx index de6cccb95..58b9caf40 100644 --- a/src/lib/pages/validator-details/components/tables/ProposedBlocksTable.tsx +++ b/src/lib/pages/validator-details/components/tables/ProposedBlocksTable.tsx @@ -70,7 +70,8 @@ export const ProposedBlocksTable = ({ {isMobile ? ( diff --git a/src/lib/pages/validator-details/components/validator-overview/index.tsx b/src/lib/pages/validator-details/components/validator-overview/index.tsx index be0d26e18..c28cd8821 100644 --- a/src/lib/pages/validator-details/components/validator-overview/index.tsx +++ b/src/lib/pages/validator-details/components/validator-overview/index.tsx @@ -70,7 +70,7 @@ export const ValidatorOverview = ({ ) : ( <> - + getValidatorUptime(endpoint, validatorAddress, blocks), { retry: 1, + refetchOnWindowFocus: false, } ); };