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

recent 100 blocks chart #836

Merged
merged 8 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -25,7 +26,8 @@ export const PenaltySection = ({ penaltyEvents }: PenaltySectionProps) => (
Latest 90 Days
</Text>
</Flex>
<Flex direction="column" gap={2}>
<Flex gap={2} alignItems="center">
<CustomIcon name="check-circle" color="success.main" />
{penaltyEvents.length === 0 ? (
<Text variant="body2" color="text.dark">
This validator never had any slash or jailed history within 90 days.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) => (
<Box
width="12px"
height="12px"
backgroundColor={isHighlighted ? "secondary.main" : "primary.main"}
borderRadius="2px"
/>
const Block = forwardRef<HTMLDivElement, BlockProps>(
({ 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 (
<Tooltip label={`${height} (${voteLabel})`}>
<Box position="relative">
<Box
width="12px"
height="12px"
backgroundColor={backgroundColor}
borderRadius="2px"
/>
{ref && (
<Box
ref={ref}
position="absolute"
bottom="-16px"
left="1px"
width="0"
height="0"
borderBottom="6px solid var(--chakra-colors-text-dark)"
borderLeft="5px solid transparent"
borderRight="5px solid transparent"
/>
)}
</Box>
</Tooltip>
);
}
);

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) => (
<Block key={`block-${_}`} isHighlighted={index === 0} />
));
const { data, isLoading, dataUpdatedAt } = useValidatorUptime(
validatorAddress,
100
);
const { isExpand } = useNavContext();
const router = useRouter();

const parentRef = useRef<HTMLDivElement>(null);
const cursorRef = useRef<HTMLDivElement>(null);
const hoverTextRef = useRef<HTMLDivElement>(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 <Loading />;
evilpeach marked this conversation as resolved.
Show resolved Hide resolved
if (!data) return <ErrorFetching dataName="recent blocks data" />;

return (
<Flex direction="column" w="full" gap={4}>
{hasTitle && (
<Flex
direction={{ base: "column", md: "row" }}
justify="space-between"
align={{ base: "start", md: "center" }}
w="full"
>
<Heading as="h6" variant="h6" color="text.main">
Most Recent 100 Blocks
</Heading>
<Text variant="body2" color="text.dark">
Latest Update: timestamp
</Text>
</Flex>
)}
<Flex
direction="column"
w="full"
gap={4}
ref={parentRef}
position="relative"
pb={10}
>
<Flex
direction={{ base: "column", md: "row" }}
justify="space-between"
align={{ base: "start", md: "center" }}
w="full"
>
<Heading as="h6" variant="h6" color="text.main">
Most Recent 100 Blocks
</Heading>
<Text variant="body2" color="text.dark">
Latest Update: {formatUTC(new Date(dataUpdatedAt))}
</Text>
</Flex>
<Grid
templateColumns="repeat(auto-fit, minmax(12px, 1fr))"
gap={1}
width="full"
>
{blocks}
{data.recent100Blocks
.map((block, index) => (
<Block
key={`block-${block.height}`}
ref={index === 0 ? cursorRef : undefined}
{...block}
/>
))
.reverse()}
</Grid>
{/* TODO: add arrow and align with last block */}
<Text variant="body2" color="text.dark">
Most Recent Blocks: 12345678
<Text
variant="body2"
color="text.dark"
position="absolute"
bottom="0"
ref={hoverTextRef}
>
Most Recent Blocks: {data.recent100Blocks[0]?.height ?? "N/A"}
</Text>
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,23 @@ 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";
import { RecentBlocksLegends } from "./RecentBlocksLegends";
import { RecentBlocksSection } from "./RecentBlocksSection";

interface UptimeSectionProps {
validatorAddress: ValidatorAddr;
uptimeData: ValidatorUptimeResponse;
uptimeBlock: number;
setUptimeBlock?: (block: number) => void;
onViewMore?: () => void;
}

export const UptimeSection = ({
validatorAddress,
uptimeData,
uptimeBlock,
setUptimeBlock,
Expand Down Expand Up @@ -109,7 +111,9 @@ export const UptimeSection = ({
<RecentBlocksLegends uptime={computed} />
{onViewMore && (
<>
{isMobile && <RecentBlocksSection />}
{isMobile && (
<RecentBlocksSection validatorAddress={validatorAddress} />
)}
{uptimeData.events.length !== 0 && <Divider />}
{uptimeData.events.map((event) => (
<PenaltyEvent key={event.height} event={event} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const Performance = ({
if (onViewMore)
return (
<UptimeSection
validatorAddress={validatorAddress}
uptimeData={uptimeData}
uptimeBlock={uptimeBlock}
onViewMore={onViewMore}
Expand All @@ -45,6 +46,7 @@ export const Performance = ({
<Flex gap={{ base: 4, md: 6 }} direction={{ base: "column", md: "row" }}>
<Flex flex={{ md: "2" }}>
<UptimeSection
validatorAddress={validatorAddress}
uptimeData={uptimeData}
uptimeBlock={uptimeBlock}
setUptimeBlock={(block) => setUptimeBlock(block)}
Expand All @@ -60,7 +62,7 @@ export const Performance = ({
rounded={8}
w="100%"
>
<RecentBlocksSection hasTitle />
<RecentBlocksSection validatorAddress={validatorAddress} />
</Flex>
<ProposedBlocksTable validatorAddress={validatorAddress} />
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export const ProposedBlocksTable = ({
<TableTitle
title="Proposed Blocks"
count={data?.total}
mb={2}
mt={2}
mb={1}
helperText="Display the proposed blocks by this validator within the last 30 days"
/>
{isMobile ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const ValidatorOverview = ({
) : (
<>
<Flex backgroundColor="gray.900" p={6} rounded={8} w="100%">
<RecentBlocksSection hasTitle />
<RecentBlocksSection validatorAddress={validatorAddress} />
</Flex>
<VotingPowerChart
validatorAddress={validatorAddress}
Expand Down
1 change: 1 addition & 0 deletions src/lib/services/validatorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export const useValidatorUptime = (
async () => getValidatorUptime(endpoint, validatorAddress, blocks),
{
retry: 1,
refetchOnWindowFocus: false,
}
);
};
Expand Down
Loading