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-396]: Feature - bonded token changes mobile #837

Merged
merged 17 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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

- [#837](https://github.com/alleslabs/celatone-frontend/pull/837) Voting power in validator detail overview
- [#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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Box, Button, Flex, Heading, Text } from "@chakra-ui/react";
import { Box, Button, Flex } from "@chakra-ui/react";
import type { BigSource } from "big.js";
import type { ScriptableContext, TooltipModel } from "chart.js";

import { TabIndex } from "../../types";
import { useInternalNavigate } from "lib/app-provider";
import { useMobile } from "lib/app-provider";
import { LineChart } from "lib/components/chart/LineChart";
import { CustomIcon } from "lib/components/icon";
import { Loading } from "lib/components/Loading";
Expand All @@ -16,32 +15,59 @@ import {
getTokenLabel,
} from "lib/utils";

import { VotingPowerDetails } from "./VotingPowerDetails";

interface VotingPowerChartProps {
validatorAddress: ValidatorAddr;
singleStakingDenom: Option<string>;
assetInfos: Option<AssetInfos>;
isOverview?: boolean;
onViewMore?: () => void;
}

export const VotingPowerChart = ({
validatorAddress,
singleStakingDenom,
assetInfos,
isOverview,
onViewMore,
}: VotingPowerChartProps) => {
const navigate = useInternalNavigate();
const isMobile = useMobile();
const isMobileOverview = isMobile && !!onViewMore;

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

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

const labels = historicalPowers?.items.map((item) =>
const assetInfo = singleStakingDenom
? assetInfos?.[singleStakingDenom]
: undefined;

if (isMobileOverview)
return (
<Flex
backgroundColor="gray.900"
p={4}
rounded={8}
w="100%"
justifyContent="space-between"
alignItems="center"
onClick={onViewMore}
>
<VotingPowerDetails
historicalPowers={historicalPowers}
singleStakingDenom={singleStakingDenom}
assetInfo={assetInfo}
/>
<CustomIcon boxSize={6} m={0} name="chevron-right" color="gray.600" />
</Flex>
);

const labels = historicalPowers.items.map((item) =>
formatHHmm(item.hourRoundedTimestamp as Date)
);

const dateLabels = labels?.map((label) => {
const dateLabels = labels.map((label) => {
const [hours, minutes] = label.split(":");
const date = new Date();
date.setHours(parseInt(hours, 10));
Expand All @@ -52,8 +78,7 @@ export const VotingPowerChart = ({
});

const dataset = {
data:
historicalPowers?.items.map((item) => item.votingPower.toNumber()) ?? [],
data: historicalPowers.items.map((item) => item.votingPower.toNumber()),
borderColor: "#D8BEFC",
backgroundColor: (context: ScriptableContext<"line">) => {
const { ctx } = context.chart;
Expand All @@ -69,40 +94,27 @@ export const VotingPowerChart = ({
pointHoverBorderColor: "#D8BEFC",
};

const assetInfo = singleStakingDenom
? assetInfos?.[singleStakingDenom]
: undefined;

const handleFormatValue = (value: string | number, isSuffix = true) =>
formatUTokenWithPrecision(
value as U<Token<BigSource>>,
assetInfo?.precision ?? 0,
isSuffix,
2
);

const isDatasetContainsData = dataset && dataset.data.length > 0;

const currency = singleStakingDenom
? `${getTokenLabel(singleStakingDenom, assetInfo?.symbol)}`
: "";
const currentPrice = isDatasetContainsData
? handleFormatValue(dataset.data[dataset.data.length - 1])
: "";
const diffInLast24Hr = isDatasetContainsData
? dataset.data[dataset.data.length - 1] - dataset.data[0]
: 0;

const customizeTooltip = (tooltip: TooltipModel<"line">) => {
const { raw, dataIndex } = tooltip.dataPoints[0];

const formattedAmount = formatUTokenWithPrecision(
raw as U<Token<BigSource>>,
assetInfo?.precision ?? 0,
false,
2
);

return `
<div style="padding: 8px 12px;">
<div style="font-weight: 700;">
<h1 style="font-size: 12px; color: #ADADC2;">${
singleStakingDenom ? "Bonded Token" : "Voting Powers"
}</h1>
<p style="font-size: 16px; color: #F7F2FE; white-space: nowrap;">${handleFormatValue(raw as number, false)} ${currency}</p>
<p style="font-size: 16px; color: #F7F2FE; white-space: nowrap;">${formattedAmount} ${currency}</p>
</div>
<hr style="margin-top: 8px; color: #68688A;"/>
<p style="margin-top: 8px; font-size: 12px; color: #F7F2FE; white-space: nowrap;">${dateLabels[dataIndex]}</p>
Expand All @@ -123,44 +135,18 @@ export const VotingPowerChart = ({
w="100%"
>
<Flex gap={6} direction="column" w={250} minW={250}>
<Flex gap={2} direction="column">
<Heading variant="h6">
{singleStakingDenom
? "Current Bonded Token"
: "Current Voting Powers"}
</Heading>
<Heading variant="h5" fontWeight={600}>
{currentPrice} {currency}
</Heading>
<Text variant="body1">
<Text
as="span"
fontWeight={700}
color={diffInLast24Hr >= 0 ? "success.main" : "error.main"}
>
{diffInLast24Hr >= 0
? `+${handleFormatValue(diffInLast24Hr)}`
: `-${handleFormatValue(-diffInLast24Hr)}`}
</Text>{" "}
{currency} in last 24 hr
</Text>
</Flex>
{isOverview && (
<VotingPowerDetails
historicalPowers={historicalPowers}
singleStakingDenom={singleStakingDenom}
assetInfo={assetInfo}
/>
{onViewMore && (
<Button
variant="ghost-secondary"
p="unset"
size="md"
pl={2}
onClick={() =>
navigate({
pathname: "/validators/[validatorAddress]/[tab]",
query: {
validatorAddress,
tab: TabIndex.BondedTokenChanges,
},
options: { shallow: true },
})
}
onClick={onViewMore}
>
See all related transactions
<CustomIcon name="chevron-right" boxSize={3} />
Expand All @@ -172,7 +158,14 @@ export const VotingPowerChart = ({
labels={labels}
dataset={dataset}
customizeTooltip={customizeTooltip}
customizeYAxisTicks={handleFormatValue}
customizeYAxisTicks={(value) =>
formatUTokenWithPrecision(
value as U<Token<BigSource>>,
assetInfo?.precision ?? 0,
false,
2
)
}
/>
</Box>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Flex, Heading, Text } from "@chakra-ui/react";

import type { HistoricalPowersResponse } from "lib/services/validator";
import { big } from "lib/types";
import type { AssetInfo, Option, Token, U } from "lib/types";
import { formatUTokenWithPrecision, getTokenLabel } from "lib/utils";

interface VotingPowerDetailsProps {
historicalPowers: HistoricalPowersResponse;
singleStakingDenom: Option<string>;
assetInfo: Option<AssetInfo>;
}

export const VotingPowerDetails = ({
historicalPowers,
singleStakingDenom,
assetInfo,
}: VotingPowerDetailsProps) => {
const currency = singleStakingDenom
? `${getTokenLabel(singleStakingDenom, assetInfo?.symbol)}`
: "";

const isHistoricalPowersContainsData =
historicalPowers && historicalPowers.items.length > 0;

const currentPrice = isHistoricalPowersContainsData
? formatUTokenWithPrecision(
historicalPowers.items[historicalPowers.items.length - 1]
.votingPower as U<Token<Big>>,
assetInfo?.precision ?? 0,
true,
2
)
: "";

const compareVotingPower = isHistoricalPowersContainsData
? historicalPowers.items[
historicalPowers.items.length - 1
].votingPower.minus(historicalPowers.items[0].votingPower)
: big(0);

const formattedVotingPower = `${compareVotingPower.gte(0) ? "+" : "-"}${formatUTokenWithPrecision(
compareVotingPower.abs() as U<Token<Big>>,
assetInfo?.precision ?? 0,
true,
2
)}`;

return (
<Flex gap={2} direction="column">
<Heading variant="h6">
{singleStakingDenom ? "Current Bonded Token" : "Current Voting Powers"}
</Heading>
<Heading variant="h5" fontWeight={600}>
{currentPrice} {currency}
</Heading>
<Text variant="body1">
<Text
as="span"
fontWeight={700}
color={compareVotingPower.gte(0) ? "success.main" : "error.main"}
>
{formattedVotingPower}
</Text>
{` ${currency}`} in last 24 hrs
</Text>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Alert, AlertDescription, Flex } from "@chakra-ui/react";
import type Big from "big.js";

import { BondedTokenChangeMobileCard } from "../bonded-token-changes/BondedTokenChangeMobileCard";
import { VotingPowerChart } from "../bonded-token-changes/VotingPowerChart";
import { Performance } from "../performance";
import { RecentBlocksSection } from "../performance/RecentBlocksSection";
Expand Down Expand Up @@ -62,24 +61,17 @@ export const ValidatorOverview = ({
onViewMore={onSelectPerformance}
/>
</Flex>
{isMobile ? (
<BondedTokenChangeMobileCard
denom="OSMO"
onViewMore={onSelectBondedTokenChanges}
/>
) : (
<>
<Flex backgroundColor="gray.900" p={6} rounded={8} w="100%">
<RecentBlocksSection hasTitle />
</Flex>
<VotingPowerChart
validatorAddress={validatorAddress}
singleStakingDenom={singleStakingDenom}
assetInfos={assetInfos}
isOverview
/>
</>
{!isMobile && (
<Flex backgroundColor="gray.900" p={6} rounded={8} w="100%">
<RecentBlocksSection hasTitle />
</Flex>
)}
<VotingPowerChart
validatorAddress={validatorAddress}
singleStakingDenom={singleStakingDenom}
assetInfos={assetInfos}
onViewMore={onSelectBondedTokenChanges}
/>
<ProposedBlocksTable
validatorAddress={validatorAddress}
onViewMore={onSelectPerformance}
Expand Down
Loading