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
- [#821](https://github.com/alleslabs/celatone-frontend/pull/821) Add Validator's proposed blocks table
- [#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
Expand Down
72 changes: 23 additions & 49 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,41 +25,33 @@ export const ValidatorBadge = ({
hasLabel = true,
moreInfo,
}: ValidatorBadgeProps) => {
const { data: valImgSrc, isLoading } = useValidatorImage(validator);
const isMobile = useMobile();

return (
<Flex alignItems="center" gap={2}>
<ValidatorImage validator={validator} boxSize={badgeSize} />
{validator ? (
<Flex direction="column">
{isMobile && hasLabel && <MobileLabel label="Validator" />}
<ExplorerLink
value={validator.moniker ?? validator.validatorAddress}
copyValue={validator.validatorAddress}
type="validator_address"
textFormat="ellipsis"
showCopyOnHover
ampCopierSection={ampCopierSection}
maxWidth={maxWidth}
fixedHeight
/>
{moreInfo}
</Flex>
) : (
<>
{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"
/>
)}
<Flex direction="column">
{isMobile && hasLabel && <MobileLabel label="Validator" />}
<ExplorerLink
value={validator.moniker ?? validator.validatorAddress}
copyValue={validator.validatorAddress}
type="validator_address"
textFormat="ellipsis"
showCopyOnHover
ampCopierSection={ampCopierSection}
maxWidth={maxWidth}
fixedHeight
/>
{moreInfo}
</Flex>
<ValidatorImage validator={validator} boxSize={badgeSize} />
<Text variant="body2" color="text.disabled">
N/A
</Text>
</>
) : (
<FallbackRender badgeSize={badgeSize} />
)}
</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, Option, Token, U, ValidatorAddr } from "lib/types";
import {
formatHHmm,
formatUTokenWithPrecision,
Expand All @@ -17,24 +15,19 @@ import {

interface VotingPowerChartProps {
validatorAddress: ValidatorAddr;
singleStakingDenom: Option<string>;
assetInfos: Option<AssetInfos>;
}

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,21 +1,27 @@
import { Flex } from "@chakra-ui/react";

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

import { VotingPowerChart } from "./VotingPowerChart";

interface BondedTokenChangesProps {
validatorAddress: ValidatorAddr;
singleStakingDenom: Option<string>;
assetInfos: Option<AssetInfos>;
}

export const BondedTokenChanges = ({
validatorAddress,
}: BondedTokenChangesProps) => {
return (
<Flex direction="column" gap={{ base: 4, md: 8 }} pt={6}>
<VotingPowerChart validatorAddress={validatorAddress} />
<RelatedTransactionTable />
</Flex>
);
};
singleStakingDenom,
assetInfos,
}: BondedTokenChangesProps) => (
<Flex direction="column" gap={{ base: 4, md: 8 }} pt={6}>
<VotingPowerChart
validatorAddress={validatorAddress}
singleStakingDenom={singleStakingDenom}
assetInfos={assetInfos}
/>
<RelatedTransactionTable />
</Flex>
);
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 @@ -6,10 +6,10 @@ import { RecentBlocksSection } from "../performance/RecentBlocksSection";
import { UptimeSection } from "../performance/UptimeSection";
import { ProposedBlocksTable } from "../tables/ProposedBlocksTable";
import { VotedProposalsTable } from "../tables/VotedProposalsTable";
import { useMobile, useMoveConfig } from "lib/app-provider";
import { useMobile } 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, Option, ValidatorAddr } from "lib/types";

import { ValidatorDescription } from "./ValidatorDescription";
import { VotingPowerOverview } from "./VotingPowerOverview";
Expand All @@ -18,64 +18,67 @@ interface ValidatorOverviewProps {
onSelectVotes: () => void;
onSelectPerformance: () => void;
onSelectBondedTokenChanges: () => void;
isActive: boolean;
isJailed: boolean;
details: string;
validatorAddress: ValidatorAddr;
singleStakingDenom: Option<string>;
assetInfos: Option<AssetInfos>;
}

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
validatorAddress={validatorAddress}
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 ? (
<BondedTokenChangeMobileCard
denom="OSMO"
Poafs1 marked this conversation as resolved.
Show resolved Hide resolved
onViewMore={onSelectBondedTokenChanges}
/>
) : (
<>
<Flex backgroundColor="gray.900" p={6} rounded={8} w="100%">
<RecentBlocksSection hasTitle />
</Flex>
<VotingPowerChart
validatorAddress={validatorAddress}
singleStakingDenom={singleStakingDenom}
assetInfos={assetInfos}
/>
</>
)}
<ProposedBlocksTable
validatorAddress={validatorAddress}
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
Loading
Loading