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

[CFE-33,34]: Feat - validator details overview section #819

Merged
merged 13 commits into from
Mar 15, 2024
Merged
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

- [#819](https://github.com/alleslabs/celatone-frontend/pull/819) Add validator detail overview section with data from APIs
- [#818](https://github.com/alleslabs/celatone-frontend/pull/818) bonded tokens voting powers replacement with real data from api v1
- [#801](https://github.com/alleslabs/celatone-frontend/pull/801) Add validator detail ui structure
- [#817](https://github.com/alleslabs/celatone-frontend/pull/817) api v1 - validator voted proposals
Expand Down
44 changes: 10 additions & 34 deletions src/lib/components/ValidatorBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { ImageProps } from "@chakra-ui/react";
import { Flex, Image, Spinner, Text } from "@chakra-ui/react";
import { Flex, Text } from "@chakra-ui/react";

import { useMobile } from "lib/app-provider";
import { ExplorerLink } from "lib/components/ExplorerLink";
import { useValidatorImage } from "lib/services/validatorService";
import type { Nullable, Validator } from "lib/types";

import { MobileLabel } from "./table/MobileLabel";
import { ValidatorImage } from "./ValidatorImage";

interface ValidatorBadgeProps {
validator: Nullable<Validator>;
Expand All @@ -17,24 +17,6 @@ interface ValidatorBadgeProps {
moreInfo?: JSX.Element;
}

const FallbackRender = ({
badgeSize,
}: {
badgeSize: ValidatorBadgeProps["badgeSize"];
}) => (
<>
<Image
boxSize={badgeSize}
src="https://raw.githubusercontent.com/alleslabs/assets/main/webapp-assets/asset/na-token.svg"
alt="N/A"
borderRadius="50%"
/>
<Text variant="body2" color="text.disabled">
N/A
</Text>
</>
);

export const ValidatorBadge = ({
validator,
badgeSize = 10,
Expand All @@ -43,24 +25,13 @@ export const ValidatorBadge = ({
hasLabel = true,
moreInfo,
}: ValidatorBadgeProps) => {
const { data: valImgSrc, isLoading } = useValidatorImage(validator);
const isMobile = useMobile();

return (
<Flex alignItems="center" gap={2}>
{validator ? (
<>
{isLoading ? (
<Spinner boxSize={badgeSize} />
) : (
<Image
boxSize={badgeSize}
src={valImgSrc}
alt={validator.moniker}
borderRadius="50%"
fallbackSrc="https://assets.alleslabs.dev/webapp-assets/placeholder/validator.svg"
fallbackStrategy="beforeLoadOrError"
/>
)}
<ValidatorImage validator={validator} boxSize={badgeSize} />
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
<Flex direction="column">
{isMobile && hasLabel && <MobileLabel label="Validator" />}
<ExplorerLink
Expand All @@ -77,7 +48,12 @@ export const ValidatorBadge = ({
</Flex>
</>
) : (
<FallbackRender badgeSize={badgeSize} />
<>
<ValidatorImage validator={validator} boxSize={badgeSize} />
<Text variant="body2" color="text.disabled">
N/A
</Text>
</>
)}
</Flex>
);
Expand Down
43 changes: 43 additions & 0 deletions src/lib/components/ValidatorImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ImageProps } from "@chakra-ui/react";
import { Image, SkeletonCircle } from "@chakra-ui/react";

import { useValidatorImage } from "lib/services/validatorService";
import type { Nullable, Validator } from "lib/types";

interface ValidatorImageProps {
validator: Nullable<Validator>;
boxSize?: ImageProps["boxSize"];
}

export const ValidatorImage = ({
validator,
boxSize = 10,
}: ValidatorImageProps) => {
const { data, isLoading } = useValidatorImage(validator);

if (!validator) {
return (
<Image
boxSize={boxSize}
minWidth={boxSize}
src="https://raw.githubusercontent.com/alleslabs/assets/main/webapp-assets/asset/na-token.svg"
alt="N/A"
borderRadius="50%"
/>
);
}

return isLoading || !data || !validator.moniker ? (
<SkeletonCircle boxSize={boxSize} />
) : (
<Image
boxSize={boxSize}
minWidth={boxSize}
src={data}
alt={validator.moniker}
borderRadius="50%"
fallbackSrc="https://assets.alleslabs.dev/webapp-assets/placeholder/validator.svg"
fallbackStrategy="beforeLoadOrError"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import { Box, Flex, Heading, Text } from "@chakra-ui/react";
import type { BigSource } from "big.js";
import type { ScriptableContext, TooltipModel } from "chart.js";

import { useCelatoneApp } from "lib/app-provider";
import { LineChart } from "lib/components/chart/LineChart";
import { Loading } from "lib/components/Loading";
import { ErrorFetching } from "lib/components/state";
import { useAssetInfos } from "lib/services/assetService";
import { useValidatorHistoricalPowers } from "lib/services/validatorService";
import type { Token, U, ValidatorAddr } from "lib/types";
import type { AssetInfos, Token, U, ValidatorAddr } from "lib/types";
import {
formatHHmm,
formatUTokenWithPrecision,
Expand All @@ -17,24 +15,19 @@ import {

interface VotingPowerChartProps {
validatorAddress: ValidatorAddr;
singleStakingDenom?: string;
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
assetInfos?: AssetInfos;
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
}

export const VotingPowerChart = ({
validatorAddress,
singleStakingDenom,
assetInfos,
}: VotingPowerChartProps) => {
const {
chainConfig: {
extra: { singleStakingDenom },
},
} = useCelatoneApp();
const { data: assetInfos, isLoading: isAssetInfosLoading } = useAssetInfos({
withPrices: false,
});

const { data: historicalPowers, isLoading } =
useValidatorHistoricalPowers(validatorAddress);

if (isLoading || isAssetInfosLoading) return <Loading />;
if (isLoading) return <Loading />;
if (!historicalPowers) return <ErrorFetching dataName="historical powers" />;

const labels = historicalPowers?.items.map((item) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { Flex } from "@chakra-ui/react";

import { RelatedTransactionTable } from "../tables/RelatedTransactionsTable";
import type { ValidatorAddr } from "lib/types";
import type { AssetInfos, ValidatorAddr } from "lib/types";

import { VotingPowerChart } from "./VotingPowerChart";

interface BondedTokenChangesProps {
validatorAddress: ValidatorAddr;
singleStakingDenom?: string;
assetInfos?: AssetInfos;
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
}

export const BondedTokenChanges = ({
validatorAddress,
singleStakingDenom,
assetInfos,
}: BondedTokenChangesProps) => {
return (
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
<Flex direction="column" gap={{ base: 4, md: 8 }} pt={6}>
<VotingPowerChart validatorAddress={validatorAddress} />
<VotingPowerChart
validatorAddress={validatorAddress}
singleStakingDenom={singleStakingDenom}
assetInfos={assetInfos}
/>
<RelatedTransactionTable />
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { Flex, Heading, Text } from "@chakra-ui/react";
import { Flex, Text } from "@chakra-ui/react";

export const ValidatorDescription = () => (
<Flex direction="column" gap={2}>
<Heading variant="h6" as="h6" color="text.main">
interface ValidatorDescriptionProps {
details: string;
}

export const ValidatorDescription = ({
details,
}: ValidatorDescriptionProps) => (
<Flex
direction="column"
gap={2}
backgroundColor="gray.900"
p={{ base: 4, md: 6 }}
rounded={8}
w="100%"
>
<Text variant="body2" fontWeight={500} as="h6" color="text.dark">
Validator Description
</Heading>
<Text variant="body1" color="text.main">
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Ullam ad vel
assumenda, temporibus maiores quidem quibusdam sapiente expedita officiis
minus! Officiis itaque nisi reprehenderit tempora eaque eos quam, in
excepturi.
</Text>
<Text variant="body2" color="text.main">
{details}
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
</Text>
</Flex>
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { VotedProposalsTable } from "../tables/VotedProposalsTable";
import { useMobile, useMoveConfig } from "lib/app-provider";
import { CustomIcon } from "lib/components/icon";
import { EmptyState } from "lib/components/state";
import type { ValidatorAddr } from "lib/types";
import type { AssetInfos, ValidatorAddr } from "lib/types";

import { ValidatorDescription } from "./ValidatorDescription";
import { VotingPowerOverview } from "./VotingPowerOverview";
Expand All @@ -18,61 +18,68 @@ interface ValidatorOverviewProps {
onSelectVotes: () => void;
onSelectPerformance: () => void;
onSelectBondedTokenChanges: () => void;
isActive: boolean;
isJailed: boolean;
details: string;
validatorAddress: ValidatorAddr;
singleStakingDenom?: string;
assetInfos?: AssetInfos;
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
}

export const ValidatorOverview = ({
onSelectVotes,
onSelectPerformance,
onSelectBondedTokenChanges,
isActive,
isJailed,
details,
validatorAddress,
singleStakingDenom,
assetInfos,
}: ValidatorOverviewProps) => {
const isMobile = useMobile();
const move = useMoveConfig({ shouldRedirect: false });

return (
<>
<Flex direction="column" gap={{ base: 4, md: 6 }} pt={6}>
<ValidatorDescription />
<Flex
gap={{ base: 4, md: 6 }}
direction={{ base: "column", md: "row" }}
>
<VotingPowerOverview />
<UptimeSection onViewMore={onSelectPerformance} />
</Flex>
{!isMobile && (
<>
<Flex backgroundColor="gray.900" p={6} rounded={8} w="100%">
<RecentBlocksSection hasTitle />
</Flex>
{!move.enabled && (
<VotingPowerChart validatorAddress={validatorAddress} />
)}
</>
)}
{isMobile && !move.enabled && (
<BondedTokenChangeMobileCard
denom="OSMO"
onViewMore={onSelectBondedTokenChanges}
/>
)}
<ProposedBlocksTable onViewMore={onSelectPerformance} />
<VotedProposalsTable onViewMore={onSelectVotes} />
</Flex>
{/* TODO: change to conditions and remove line 72-74 */}
{/* Inactive */}
<Flex bg="teal.400" my={6}>
INACTIVE
return isActive && !isJailed ? (
<Flex direction="column" gap={{ base: 4, md: 6 }} pt={6}>
<ValidatorDescription details={details} />
<Flex gap={{ base: 4, md: 6 }} direction={{ base: "column", md: "row" }}>
<VotingPowerOverview />
<UptimeSection onViewMore={onSelectPerformance} />
</Flex>
{!isMobile && (
<>
<Flex backgroundColor="gray.900" p={6} rounded={8} w="100%">
<RecentBlocksSection hasTitle />
</Flex>
{!move.enabled && (
<VotingPowerChart
validatorAddress={validatorAddress}
singleStakingDenom={singleStakingDenom}
assetInfos={assetInfos}
/>
)}
</>
)}
{isMobile && !move.enabled && (
<BondedTokenChangeMobileCard
denom="OSMO"
onViewMore={onSelectBondedTokenChanges}
/>
)}
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
<ProposedBlocksTable onViewMore={onSelectPerformance} />
<VotedProposalsTable onViewMore={onSelectVotes} />
</Flex>
) : (
<>
<Alert variant="error" gap={2} my={6}>
<CustomIcon name="alert-circle-solid" boxSize={4} color="error.main" />
<AlertDescription>
This validator is currently inactive. The information displayed
reflects the latest available data.
This validator is currently {isJailed ? "jailed" : "inactive"}. The
information displayed reflects the latest available data.
</AlertDescription>
</Alert>
<ValidatorDescription />
<ValidatorDescription details={details} />
<EmptyState
message="This validator has recently begun their duties. Let's extend our best wishes for their success in their role. 😊"
imageVariant="empty"
Expand Down

This file was deleted.

Loading
Loading