Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
kien-ngo committed Nov 27, 2024
1 parent dcd6b94 commit 1bf3114
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 44 deletions.
45 changes: 40 additions & 5 deletions packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ import { fadeInAnimation } from "../design-system/animations.js";
import { StyledButton } from "../design-system/elements.js";
import { AccountAddress } from "../prebuilt/Account/address.js";
import { AccountAvatar } from "../prebuilt/Account/avatar.js";
import { AccountBalance } from "../prebuilt/Account/balance.js";
import {
AccountBalance,
type AccountBalanceFormatParams,
} from "../prebuilt/Account/balance.js";
import { AccountBlobbie } from "../prebuilt/Account/blobbie.js";
import { AccountName } from "../prebuilt/Account/name.js";
import { AccountProvider } from "../prebuilt/Account/provider.js";
Expand Down Expand Up @@ -278,12 +281,13 @@ export const ConnectedWalletDetails: React.FC<{
chain={walletChain}
loadingComponent={<Skeleton height={fontSize.xs} width="70px" />}
fallbackComponent={<Skeleton height={fontSize.xs} width="70px" />}
formatFn={formatBalanceOnButton}
formatFn={formatAccountBalanceForButton}
tokenAddress={
props.detailsButton?.displayBalanceToken?.[
Number(walletChain?.id)
]
}
showFiatValue="USD"
/>
</Text>
</Container>
Expand Down Expand Up @@ -380,11 +384,12 @@ function DetailsModal(props: {
<AccountBalance
fallbackComponent={<Skeleton height="1em" width="100px" />}
loadingComponent={<Skeleton height="1em" width="100px" />}
formatFn={(num: number) => formatNumber(num, 9)}
chain={walletChain}
tokenAddress={
props.displayBalanceToken?.[Number(walletChain?.id)]
}
formatFn={formatAccountBalanceForModal}
showFiatValue="USD"
/>
</Text>
</Text>
Expand Down Expand Up @@ -1006,8 +1011,38 @@ function DetailsModal(props: {
);
}

function formatBalanceOnButton(num: number) {
return formatNumber(num, num < 1 ? 5 : 4);
/**
* Format the display balance for both crypto and fiat, in the Details button and Modal
* If both crypto balance and fiat balance exist, we have to keep the string very short to avoid UI issues.
* @internal
*/
function formatAccountBalanceForButton(
props: AccountBalanceFormatParams,
): string {
if (props.fiatBalance && props.fiatSymbol) {
// Need to keep them short to avoid UI overflow issues
const formattedTokenBalance = formatNumber(props.tokenBalance, 1);
const formattedFiatBalance = formatNumber(props.fiatBalance, 0);
return `${formattedTokenBalance} ${props.tokenSymbol} (${props.fiatSymbol}${formattedFiatBalance})`;
}
const formattedTokenBalance = formatNumber(
props.tokenBalance,
props.tokenBalance < 1 ? 5 : 4,
);
return `${formattedTokenBalance} ${props.tokenSymbol}`;
}

function formatAccountBalanceForModal(
props: AccountBalanceFormatParams,
): string {
if (props.fiatBalance && props.fiatSymbol) {
// Need to keep them short to avoid UI overflow issues
const formattedTokenBalance = formatNumber(props.tokenBalance, 5);
const formattedFiatBalance = formatNumber(props.fiatBalance, 4);
return `${formattedTokenBalance} ${props.tokenSymbol} (${props.fiatSymbol}${formattedFiatBalance})`;
}
const formattedTokenBalance = formatNumber(props.tokenBalance, 9);
return `${formattedTokenBalance} ${props.tokenSymbol}`;
}

const WalletInfoButton = /* @__PURE__ */ StyledButton((_) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,11 @@
import { describe, expect, it } from "vitest";
import { ANVIL_CHAIN } from "~test/chains.js";
import { render, screen, waitFor } from "~test/react-render.js";
import { TEST_CLIENT } from "~test/test-clients.js";
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
import { getWalletBalance } from "../../../../../wallets/utils/getWalletBalance.js";
import { AccountBalance } from "./balance.js";
import { AccountProvider } from "./provider.js";

describe.runIf(process.env.TW_SECRET_KEY)("AccountBalance component", () => {
it("format the balance properly", async () => {
const roundTo1Decimal = (num: number): number => Math.round(num * 10) / 10;
const balance = await getWalletBalance({
chain: ANVIL_CHAIN,
client: TEST_CLIENT,
address: TEST_ACCOUNT_A.address,
});

render(
<AccountProvider address={TEST_ACCOUNT_A.address} client={TEST_CLIENT}>
<AccountBalance chain={ANVIL_CHAIN} formatFn={roundTo1Decimal} />
</AccountProvider>,
);

waitFor(() =>
expect(
screen.getByText(roundTo1Decimal(Number(balance.displayValue)), {
exact: true,
selector: "span",
}),
).toBeInTheDocument(),
);
});

it("should fallback properly if failed to load", () => {
render(
<AccountProvider address={TEST_ACCOUNT_A.address} client={TEST_CLIENT}>
Expand Down
81 changes: 68 additions & 13 deletions packages/thirdweb/src/react/web/ui/prebuilt/Account/balance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
import type React from "react";
import type { JSX } from "react";
import type { Chain } from "../../../../../chains/types.js";
import { NATIVE_TOKEN_ADDRESS } from "../../../../../constants/addresses.js";
import { convertCryptoToFiat } from "../../../../../exports/pay.js";
import { useActiveWalletChain } from "../../../../../react/core/hooks/wallets/useActiveWalletChain.js";
import {
type GetWalletBalanceResult,
getWalletBalance,
} from "../../../../../wallets/utils/getWalletBalance.js";
import { getWalletBalance } from "../../../../../wallets/utils/getWalletBalance.js";
import { useAccountContext } from "./provider.js";

/**
* @internal
*/
export type AccountBalanceFormatParams = {
tokenBalance: number;
tokenSymbol: string;
fiatBalance?: number;
fiatSymbol?: string;
};

/**
* Props for the AccountBalance component
* @component
Expand All @@ -33,7 +42,7 @@ export interface AccountBalanceProps
* use this function to transform the balance display value like round up the number
* Particularly useful to avoid overflowing-UI issues
*/
formatFn?: (num: number) => number;
formatFn?: (props: AccountBalanceFormatParams) => string;
/**
* This component will be shown while the balance of the account is being fetched
* If not passed, the component will return `null`.
Expand Down Expand Up @@ -67,9 +76,11 @@ export interface AccountBalanceProps
* Optional `useQuery` params
*/
queryOptions?: Omit<
UseQueryOptions<GetWalletBalanceResult>,
UseQueryOptions<AccountBalanceFormatParams>,
"queryFn" | "queryKey"
>;

showFiatValue?: "USD";
}

/**
Expand Down Expand Up @@ -149,10 +160,11 @@ export interface AccountBalanceProps
export function AccountBalance({
chain,
tokenAddress,
formatFn,
loadingComponent,
fallbackComponent,
queryOptions,
formatFn,
showFiatValue,
...restProps
}: AccountBalanceProps) {
const { address, client } = useAccountContext();
Expand All @@ -165,19 +177,59 @@ export function AccountBalance({
address || "0x0",
{ tokenAddress },
] as const,
queryFn: async () => {
queryFn: async (): Promise<AccountBalanceFormatParams> => {
if (!chainToLoad) {
throw new Error("chain is required");
}
if (!client) {
throw new Error("client is required");
}
return getWalletBalance({
const tokenBalanceData = await getWalletBalance({
chain: chainToLoad,
client,
address,
tokenAddress,
});

if (!tokenBalanceData) {
throw new Error(
`Failed to retrieve ${tokenAddress ? `token: ${tokenAddress}` : "native token"} balance for address: ${address} on chainId:${chainToLoad.id}`,
);
}

if (showFiatValue) {
const fiatData = await convertCryptoToFiat({
fromAmount: Number(tokenBalanceData.displayValue),
fromTokenAddress: tokenAddress || NATIVE_TOKEN_ADDRESS,
to: showFiatValue,
chain: chainToLoad,
client,
}).catch(() => undefined);

// We can never support 100% of token out there, so if something fails to resolve, it's expected
// in that case just return the tokenBalance and symbol
return {
tokenBalance: Number(tokenBalanceData.displayValue),
tokenSymbol: tokenBalanceData.symbol,
fiatBalance: fiatData?.result,
fiatSymbol: fiatData?.result
? new Intl.NumberFormat("en", {
style: "currency",
currency: showFiatValue,
minimumFractionDigits: 0,
maximumFractionDigits: 0,
})
.formatToParts(0)
.find((p) => p.type === "currency")?.value ||
showFiatValue.toUpperCase()
: undefined,
};
}

return {
tokenBalance: Number(tokenBalanceData.displayValue),
tokenSymbol: tokenBalanceData.symbol,
};
},
...queryOptions,
});
Expand All @@ -190,13 +242,16 @@ export function AccountBalance({
return fallbackComponent || null;
}

const displayValue = formatFn
? formatFn(Number(balanceQuery.data.displayValue))
: balanceQuery.data.displayValue;
if (formatFn) {
return <span {...restProps}>{formatFn(balanceQuery.data)}</span>;
}

const { tokenBalance, tokenSymbol, fiatBalance, fiatSymbol } =
balanceQuery.data;

return (
<span {...restProps}>
{displayValue} {balanceQuery.data.symbol}
{`${tokenBalance} ${tokenSymbol} (${fiatSymbol} ${fiatBalance})`}
</span>
);
}

0 comments on commit 1bf3114

Please sign in to comment.