Add account balances

This commit is contained in:
Yana
2024-12-17 20:28:52 +07:00
parent 601863419b
commit 5409fe0ec2
10 changed files with 360 additions and 137 deletions
+60 -31
View File
@@ -1,10 +1,46 @@
import { NYM_ACCOUNT_ADDRESS } from "@/app/api/urls";
import ExplorerCard from "@/components/cards/ExplorerCard";
import { NYM_ACCOUNT_ADDRESS, NYM_PRICES_API } from "@/app/api/urls";
import { AccountBalancesCard } from "@/components/accountPageComponents/AccountBalancesCard";
import { AccountInfoCard } from "@/components/accountPageComponents/AccountInfoCard";
import { ContentLayout } from "@/components/contentLayout/ContentLayout";
import SectionHeading from "@/components/headings/SectionHeading";
import ExplorerListItem from "@/components/list/ListItem";
import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton";
import { Box, Grid2, Stack } from "@mui/material";
import { Box, Grid2 } from "@mui/material";
interface IRewardDetails {
amount_staked: IAmountDetails;
node_id: number;
node_still_fully_bonded: boolean;
rewards: IAmountDetails;
}
interface IAmountDetails {
denom: string;
amount: string;
}
interface IDelegationDetails {
node_id: number;
delegated: IAmountDetails;
height: number;
proxy: null | string;
}
interface ITotalDetails {
amount: string;
denom: string;
}
export interface IAccountInfo {
accumulated_rewards: IRewardDetails[];
address: string;
balances: IAmountDetails[];
claimable_rewards: IAmountDetails;
delegations: IDelegationDetails[];
operator_rewards: null | any;
total_delegations: ITotalDetails;
total_value: ITotalDetails;
vesting_account: null | any;
}
export default async function Account({
params,
@@ -12,7 +48,7 @@ export default async function Account({
params: Promise<{ address: string }>;
}) {
// const address = (await params).address;
const address = "n17hllefp8rn3ayk23rsgp7agtxkv56mazv3w637";
const address = "n1z0msxu8c098umdhnthpr2ac3ck2n3an97dm8pn";
const nymAccountAddress = `${NYM_ACCOUNT_ADDRESS}${address}`;
const accountData = await fetch(nymAccountAddress, {
@@ -23,12 +59,25 @@ export default async function Account({
next: { revalidate: 60 },
// refresh event list cache at given interval
});
const nymAccountData = await accountData.json();
const nymAccountData: IAccountInfo = await accountData.json();
if (!nymAccountData) {
return null;
}
const nymPrice = await fetch(NYM_PRICES_API, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
next: { revalidate: 60 },
// refresh event list cache at given interval
});
const nymPriceData = await nymPrice.json();
console.log("nymPriceData :>> ", nymPriceData);
console.log("nymAccountData :>> ", nymAccountData);
return (
<ContentLayout>
@@ -51,33 +100,13 @@ export default async function Account({
</Box>
</Grid2>
<Grid2 size={4}>
<ExplorerCard label="Action" sx={{ height: "100%" }}>
<div />
</ExplorerCard>
<AccountInfoCard accountInfo={nymAccountData} />
</Grid2>
<Grid2 size={8}>
<ExplorerCard label="Basic info">
<Stack gap={1}>
<ExplorerListItem
divider
label="NYM Address"
value="0x1234567890"
/>
<ExplorerListItem
divider
label="Identity Key"
value="0x1234567890"
/>
<ExplorerListItem
row
divider
label="Node bonded"
value="24/11/2024"
/>
<ExplorerListItem row divider label="Nr. of stakes" value="56" />
<ExplorerListItem row label="Self bonded" value="10,000 NYM" />
</Stack>
</ExplorerCard>
<AccountBalancesCard
accountInfo={nymAccountData}
nymPrice={nymPriceData.usd}
/>
</Grid2>
</Grid2>
</ContentLayout>
+3
View File
@@ -29,3 +29,6 @@ export const NYM_NODE_BONDED =
export const NYM_ACCOUNT_ADDRESS =
"https://explorer.nymtech.net/api/v1/tmp/unstable/account/";
export const NYM_PRICES_API =
"https://canary-nym-vpn-chain-payment-watcher.nymte.ch/v1/price/average";
+2 -2
View File
@@ -1,4 +1,4 @@
import type { IAccountStatsCardProps } from "@/components/cards/AccountStatsCard";
import type { IAccountBalancesTableProps } from "@/components/cards/AccountBalancesTable";
import type { ContentCardProps } from "@/components/cards/MonoCard";
const explorerCard: ContentCardProps = {
@@ -78,7 +78,7 @@ const explorerCard: ContentCardProps = {
},
};
const accountStatsCard: IAccountStatsCardProps = {
const accountStatsCard: IAccountBalancesTableProps = {
overTitle: "Total value",
priceTitle: 1990.0174,
rows: [
@@ -0,0 +1,131 @@
"use client";
import type { IAccountInfo } from "@/app/account/[id]/page";
import ExplorerCard from "../cards/ExplorerCard";
import { runTurboTracing } from "next/dist/build/swc/generated-native";
import { AccountBalancesTable } from "../cards/AccountBalancesTable";
export interface IAccontStatsRowProps {
type: string;
allocation: number;
amount: number;
value: number;
history?: { type: string; amount: number }[];
isLastRow?: boolean;
progressBarColor?: string;
}
interface IAccountBalancesCardProps {
accountInfo: IAccountInfo;
nymPrice: number;
}
const getNymsFormated = (unyms: number) => {
const balance = unyms / 1000000;
return balance;
};
const getPriceInUSD = (unyms: number, usdPrice: number) => {
const balanceInUSD = (unyms / 1000000) * usdPrice;
const balanceFormated = Number(balanceInUSD.toFixed(2));
return balanceFormated;
};
const getAllocation = (unyms: number, totalUnyms: number): number => {
const allocationPercentage = (unyms * 100) / totalUnyms;
return Number(allocationPercentage.toFixed(2));
};
export const AccountBalancesCard = (props: IAccountBalancesCardProps) => {
const { accountInfo, nymPrice } = props;
const totalBalanceUSD = getPriceInUSD(
Number(accountInfo.total_value.amount),
nymPrice,
);
const spendableNYM = getNymsFormated(Number(accountInfo.balances[0].amount));
const spendableUSD = getPriceInUSD(
Number(accountInfo.balances[0].amount),
nymPrice,
);
const spendableAllocation = getAllocation(
Number(accountInfo.balances[0].amount),
Number(accountInfo.total_value.amount),
);
const delegationsNYM = getNymsFormated(
Number(accountInfo.total_delegations.amount),
);
const delegationsUSD = getPriceInUSD(
Number(accountInfo.total_delegations.amount),
nymPrice,
);
const delegationsAllocation = getAllocation(
Number(accountInfo.total_delegations.amount),
Number(accountInfo.total_value.amount),
);
const claimableNYM = getNymsFormated(
Number(accountInfo.claimable_rewards.amount),
);
const claimableUSD = getPriceInUSD(
Number(accountInfo.claimable_rewards.amount),
nymPrice,
);
const claimableAllocation = getAllocation(
Number(accountInfo.claimable_rewards.amount),
Number(accountInfo.total_value.amount),
);
const tableRows = [
{
type: "Spendable",
allocation: spendableAllocation,
amount: spendableNYM,
value: spendableUSD,
},
{
type: "Delegated",
allocation: delegationsAllocation,
amount: delegationsNYM,
value: delegationsUSD,
history: [
{ type: "Liquid", amount: 6900 },
{ type: "Locked", amount: 6900 },
],
},
{
type: "Claimable",
allocation: claimableAllocation,
amount: claimableNYM,
value: claimableUSD,
history: [
{ type: "Unlocked", amount: 6900 },
{ type: "Staking rewards", amount: 6900 },
{ type: "Operator comission", amount: 6900 },
],
},
{
type: "Self bonded",
allocation: 15.53,
amount: 12800,
value: 1200,
},
{
type: "Locked",
allocation: 15.53,
amount: 12800,
value: 1200,
},
];
return (
<ExplorerCard
label="Total value"
title={`$ ${totalBalanceUSD}`}
sx={{ height: "100%" }}
>
<AccountBalancesTable rows={tableRows} />
</ExplorerCard>
);
};
@@ -0,0 +1,48 @@
"use client";
import type { IAccountInfo } from "@/app/account/[id]/page";
import { Box, Stack, Typography } from "@mui/material";
import ExplorerCard from "../cards/ExplorerCard";
import CopyToClipboard from "../copyToClipboard/CopyToClipboard";
import ExplorerListItem from "../list/ListItem";
import { CardQRCode } from "../qrCode/QrCode";
interface IAccountInfoCardProps {
accountInfo: IAccountInfo;
}
export const AccountInfoCard = (props: IAccountInfoCardProps) => {
const { accountInfo } = props;
const balance = Number(accountInfo.balances[0].amount) / 1000000;
const balanceFormated = `${balance} NYM`;
return (
<ExplorerCard
label="Address"
title={balanceFormated}
sx={{ height: "100%" }}
>
<Stack gap={5}>
<Box display={"flex"} justifyContent={"flex-start"}>
<CardQRCode url={accountInfo.address} />
</Box>
<ExplorerListItem
label="Address"
value={
<Stack
direction="row"
gap={0.1}
alignItems="center"
justifyContent="space-between"
width="100%"
>
<Typography variant="body4">{accountInfo.address}</Typography>
<CopyToClipboard text={accountInfo.address} />
</Stack>
}
/>
</Stack>
</ExplorerCard>
);
};
@@ -235,14 +235,12 @@ const Row = (props: IAccontStatsRowProps) => {
);
};
export interface IAccountStatsCardProps {
export interface IAccountBalancesTableProps {
rows: Array<IAccontStatsRowProps>;
overTitle?: string;
priceTitle?: number;
}
export const AccountStatsCard = (props: IAccountStatsCardProps) => {
const { rows, overTitle, priceTitle } = props;
export const AccountBalancesTable = (props: IAccountBalancesTableProps) => {
const { rows } = props;
const tablet = useMediaQuery(TABLET_WIDTH);
const progressBarPercentages = () => {
return rows.map((row) => row.allocation);
@@ -262,80 +260,63 @@ export const AccountStatsCard = (props: IAccountStatsCardProps) => {
const progressValues = getProgressValues();
return (
<Card sx={{ height: "100%", borderRadius: "unset", padding: 1 }}>
<CardContent>
{overTitle && (
<Typography
mb={3}
textTransform={"uppercase"}
variant="h5"
sx={{ color: "pine.600", letterSpacing: 0.7 }}
>
{overTitle}
</Typography>
)}
{priceTitle && (
<Typography variant="h3" sx={{ color: "pine.950" }} mb={3}>
${priceTitle}
</Typography>
)}
{!tablet && <MultiSegmentProgressBar values={progressValues} />}
<TableContainer>
<Table aria-label="collapsible table" sx={{ marginBottom: 3 }}>
<TableHead>
{tablet ? (
<TableRow>
<TableCell>
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Type
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Allocation
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Amount
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Value
</Typography>
</TableCell>
<TableCell />
</TableRow>
) : (
<TableRow>
<TableCell>
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Type
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Amount / Value
</Typography>
</TableCell>
<TableCell />
</TableRow>
)}
</TableHead>
<TableBody>
{rows.map((row, i) => (
<Row
key={row.type}
{...row}
isLastRow={i === rows.length - 1}
progressBarColor={progressBarColours[i]}
/>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
<Box>
{!tablet && <MultiSegmentProgressBar values={progressValues} />}
<TableContainer>
<Table aria-label="collapsible table" sx={{ marginBottom: 3 }}>
<TableHead>
{tablet ? (
<TableRow>
<TableCell>
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Type
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Allocation
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Amount
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Value
</Typography>
</TableCell>
<TableCell />
</TableRow>
) : (
<TableRow>
<TableCell>
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Type
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
Amount / Value
</Typography>
</TableCell>
<TableCell />
</TableRow>
)}
</TableHead>
<TableBody>
{rows.map((row, i) => (
<Row
key={row.type}
{...row}
isLastRow={i === rows.length - 1}
progressBarColor={progressBarColours[i]}
/>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
);
};
@@ -16,6 +16,7 @@ import {
UpDownPriceIndicator,
} from "../price/UpDownPriceIndicator";
import type { IDynamicProgressBarProps } from "../progressBars/EpochProgressBar";
import { CardQRCode, type ICardQRCodeProps } from "../qrCode/QrCode";
import { StarRating } from "../starRating";
interface ICardTitlePriceProps {
@@ -126,26 +127,6 @@ const CardCopyAddress = (props: ICardCopyAddressProps) => {
);
};
interface ICardQRCodeProps {
url: string;
}
const CardQRCode = (props: ICardQRCodeProps) => {
const { url } = props;
return (
<Box display={"flex"} justifyContent={"flex-start"}>
<Box
padding={1}
border={"1px solid #C3D7D7"}
display={"block"}
width={"unset"}
>
<QRCodeCanvas value={url} />
</Box>
</Box>
);
};
interface ICardRatingsProps {
ratings: Array<{ title: string; numberOfStars: number }>;
}
@@ -1,7 +1,8 @@
import type { IBondInfo, INodeDescription } from "@/app/api";
import { Stack } from "@mui/material";
import { Stack, Typography } from "@mui/material";
import { format } from "date-fns";
import ExplorerCard from "../cards/ExplorerCard";
import CopyToClipboard from "../copyToClipboard/CopyToClipboard";
import ExplorerListItem from "../list/ListItem";
interface IBasicInfoCardProps {
@@ -25,12 +26,40 @@ export const BasicInfoCard = (props: IBasicInfoCardProps) => {
<ExplorerListItem
divider
label="NYM Address"
value={bondInfo.bond_information.owner}
value={
<Stack
direction="row"
gap={0.1}
alignItems="center"
justifyContent="space-between"
width="100%"
>
<Typography variant="body4">
{bondInfo.bond_information.owner}
</Typography>
<CopyToClipboard text={bondInfo.bond_information.owner} />
</Stack>
}
/>
<ExplorerListItem
divider
label="Identity Key"
value={bondInfo.bond_information.node.identity_key}
value={
<Stack
direction="row"
gap={0.1}
alignItems="center"
justifyContent="space-between"
width="100%"
>
<Typography variant="body4">
{bondInfo.bond_information.node.identity_key}
</Typography>
<CopyToClipboard
text={bondInfo.bond_information.node.identity_key}
/>
</Stack>
}
/>
<ExplorerListItem row divider label="Node bonded" value={timeBonded} />
<ExplorerListItem
@@ -22,7 +22,6 @@ export const NodeMetricsCard = (props: INodeMetricsCardProps) => {
label="Host"
value={nodeDescription.description.host_information.ip_address.toString()}
/>
<ExplorerListItem row divider label="Staker rew." value="10,000 NYM" />
<ExplorerListItem row divider label="Version" value="1.1.1.1" />
<ExplorerListItem row label="Active set Prob." value="High" />
</ExplorerCard>
@@ -0,0 +1,22 @@
import { Box } from "@mui/material";
import { QRCodeCanvas } from "qrcode.react";
export interface ICardQRCodeProps {
url: string;
}
export const CardQRCode = (props: ICardQRCodeProps) => {
const { url } = props;
return (
<Box
border={"1px solid #C3D7D7"}
display={"flex"}
justifyContent={"center"}
alignItems={"center"}
width={144}
height={144}
>
<QRCodeCanvas value={url} />
</Box>
);
};