diff --git a/client/src/app/components/CardInfo.tsx b/client/src/app/components/CardInfo.tsx index 5b8619c41..c53f29a8b 100644 --- a/client/src/app/components/CardInfo.tsx +++ b/client/src/app/components/CardInfo.tsx @@ -1,16 +1,18 @@ import React from "react"; import CopyButton from "~app/components/CopyButton"; import Tooltip from "~app/components/Tooltip"; +import classNames from "classnames"; + import "./CardInfo.scss"; export interface CardInfoDetail { title: string; value?: number | string | null; onClickValue?: () => void; - showCopyBtn?: boolean; + copyValue?: string; } -export interface CardInfoProps { +interface CardInfoProps { /** * The title of the card. */ @@ -18,16 +20,19 @@ export interface CardInfoProps { tooltip?: string; value?: number | string | null; onClickValue?: () => void; - showCopyBtn?: boolean; + copyValue?: string; details?: (CardInfoDetail | null)[]; + options?: { + headerDirectionRow?: boolean; + }; } -export const CardInfo = ({ details, tooltip, title, value, onClickValue = () => {}, showCopyBtn }: CardInfoProps) => { +export const CardInfo = ({ options, details, tooltip, title, value, onClickValue = () => {}, copyValue }: CardInfoProps) => { const detailsFiltered = (details?.filter((i) => i !== null) as CardInfoDetail[]) || []; return (
-
+
{title}
{!!tooltip && ( @@ -38,16 +43,14 @@ export const CardInfo = ({ details, tooltip, title, value, onClickValue = () =>
)}
- {!!value && ( -
-
{value}
- {isValueNotZero(value) && showCopyBtn && ( -
- -
- )} -
- )} +
+
{value}
+ {isValueNotZero(value) && !!copyValue && ( +
+ +
+ )} +
{!!detailsFiltered.length && (
@@ -57,9 +60,9 @@ export const CardInfo = ({ details, tooltip, title, value, onClickValue = () =>
{detail.title}
{detail.value !== null ? detail.value : "-"} - {isValueNotZero(detail.value) && detail.showCopyBtn && ( + {isValueNotZero(detail.value) && !!detail.copyValue && (
- +
)}
diff --git a/client/src/app/components/nova/address/AddressBalance.scss b/client/src/app/components/nova/address/AddressBalance.scss index 71529007f..c8d06783a 100644 --- a/client/src/app/components/nova/address/AddressBalance.scss +++ b/client/src/app/components/nova/address/AddressBalance.scss @@ -1,6 +1,6 @@ @import "../../../../scss/media-queries"; -.balance-wrapper { +.balance-wrapper.nova { display: flex; flex-direction: column; gap: 24px; diff --git a/client/src/app/components/nova/address/AddressBalance.tsx b/client/src/app/components/nova/address/AddressBalance.tsx index 53d123031..1b7b2e4d2 100644 --- a/client/src/app/components/nova/address/AddressBalance.tsx +++ b/client/src/app/components/nova/address/AddressBalance.tsx @@ -4,6 +4,7 @@ import { IManaBalance } from "~/models/api/nova/address/IAddressBalanceResponse" import { formatAmount } from "~helpers/stardust/valueFormatHelper"; import "./AddressBalance.scss"; import { CardInfo, CardInfoDetail } from "~app/components/CardInfo"; +import { NumberHelper } from "~helpers/numberHelper"; interface AddressBalanceProps { /** @@ -36,6 +37,21 @@ interface AddressBalanceProps { readonly storageDeposit?: number | null; } +type FormatField = + | "baseTokenBalance" + | "conditionalBaseTokenBalance" + | "storageDeposit" + | "availableManaBalance" + | "availableStoredMana" + | "availableDecayMana" + | "availablePotentialMana" + | "blockIssuanceCredits" + | "manaRewards" + | "conditionallyLockedMana" + | "conditionalStoredMana" + | "conditionalDecayMana" + | "conditionalPotentialMana"; + const CONDITIONAL_BALANCE_INFO = "These funds reside within outputs with additional unlock conditions which might be potentially un-lockable"; @@ -49,10 +65,23 @@ const AddressBalance: React.FC = ({ storageDeposit, }) => { const { tokenInfo, manaInfo } = useNetworkInfoNova((s) => s.networkInfo); - const [formatBaseTokenBalanceFull, setFormatBaseTokenBalanceFull] = useState(false); - const [formatManaBalanceFull, setFormatManaBalanceFull] = useState(false); - const [formatConditionalBalanceFull, setFormatConditionalBalanceFull] = useState(false); - const [formatStorageBalanceFull, setFormatStorageBalanceFull] = useState(false); + const [isFormat, setIsFormat] = useState<{ [k in FormatField]: boolean }>({ + baseTokenBalance: false, + conditionalBaseTokenBalance: false, + storageDeposit: false, + + availableManaBalance: false, + availableStoredMana: false, + availableDecayMana: false, + availablePotentialMana: false, + blockIssuanceCredits: false, + manaRewards: false, + + conditionallyLockedMana: false, + conditionalStoredMana: false, + conditionalDecayMana: false, + conditionalPotentialMana: false, + }); if (totalBaseTokenBalance === null) { return null; @@ -75,72 +104,125 @@ const AddressBalance: React.FC = ({ availablePotentialMana === null || totalPotentialMana === null ? null : totalPotentialMana - availablePotentialMana; const availableBaseTokenAmount = (() => { - const balance = shouldShowExtendedBalance ? availableBaseTokenBalance : totalBaseTokenBalance; - return balance && balance > 0 ? formatAmount(balance, tokenInfo, formatBaseTokenBalanceFull) : 0; + const balance = (shouldShowExtendedBalance ? availableBaseTokenBalance : totalBaseTokenBalance) || 0; + return { + formatted: formatAmount(balance, tokenInfo, isFormat.baseTokenBalance), + full: balance, + }; })(); const manaFactory = ( mana: bigint | number | null | undefined, title: string, isFormat: boolean, - setFormat: React.Dispatch>, + toggleFormat: () => void, ): CardInfoDetail | null => { - if (mana !== null && mana !== undefined && mana > 0) { + if (mana !== null && mana !== undefined) { return { title: title, value: formatAmount(mana, manaInfo, isFormat), - onClickValue: () => setFormat(!isFormat), - showCopyBtn: true, + onClickValue: toggleFormat, + copyValue: String(mana), }; } - return null; + return { + title: title, + value: formatAmount(0, manaInfo, isFormat), + onClickValue: toggleFormat, + copyValue: "0", + }; + }; + + const availableManaSum = NumberHelper.sumValues( + availableStoredMana, + availableDecayMana, + availablePotentialMana, + blockIssuanceCredits, + manaRewards, + ); + const conditionallyLockedManaSum = NumberHelper.sumValues(conditionalStoredMana, conditionalDecayMana, conditionalPotentialMana); + + const toggleFormat = (field: FormatField) => () => { + setIsFormat((prev) => ({ ...prev, [field]: !prev[field] })); }; return ( -
+
setFormatBaseTokenBalanceFull(!formatBaseTokenBalanceFull)} - showCopyBtn + value={availableBaseTokenAmount.formatted} + onClickValue={toggleFormat("baseTokenBalance")} + copyValue={String(availableBaseTokenAmount.full)} /> {shouldShowExtendedBalance && ( setFormatConditionalBalanceFull(!formatConditionalBalanceFull)} + value={formatAmount(conditionalBaseTokenBalance, tokenInfo, isFormat.conditionalBaseTokenBalance)} + onClickValue={toggleFormat("conditionalBaseTokenBalance")} tooltip={CONDITIONAL_BALANCE_INFO} - showCopyBtn + copyValue={String(conditionalBaseTokenBalance)} /> )} setFormatStorageBalanceFull(!formatStorageBalanceFull)} - showCopyBtn + value={formatAmount(storageDeposit || 0, tokenInfo, isFormat.storageDeposit)} + onClickValue={toggleFormat("storageDeposit")} + copyValue={String(storageDeposit || 0)} />
diff --git a/client/src/helpers/numberHelper.ts b/client/src/helpers/numberHelper.ts index 924d46406..67db01358 100644 --- a/client/src/helpers/numberHelper.ts +++ b/client/src/helpers/numberHelper.ts @@ -16,4 +16,17 @@ export class NumberHelper { public static isNumber(value?: number | null): boolean { return value !== null && value !== undefined && !isNaN(value); } + + public static sumValues(...args: (bigint | number | string | null | undefined)[]): number { + return args.reduce((acc, cur) => { + const value = cur || 0; // Convert null or undefined to 0 + if (typeof value === "bigint" || typeof value === "number") { + return acc + Number(value); + } else if (typeof value === "string") { + return acc + Number(value) || 0; + } else { + return acc; + } + }, 0); + } }