Skip to content

Commit

Permalink
feat: add balances shimmer loader, closes #5119
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed Mar 28, 2024
1 parent a985e0f commit 53a8b08
Show file tree
Hide file tree
Showing 26 changed files with 233 additions and 77 deletions.
10 changes: 8 additions & 2 deletions src/app/common/hooks/balance/btc/use-btc-balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/marke

export function useBtcAssetBalance(btcAddress: string) {
const btcMarketData = useCryptoCurrencyMarketData('BTC');
const btcAssetBalance = useNativeSegwitBalance(btcAddress);
const {
btcBalance: btcAssetBalance,
isLoading,
isInitialLoading,
} = useNativeSegwitBalance(btcAddress);

return useMemo(
() => ({
Expand All @@ -23,7 +27,9 @@ export function useBtcAssetBalance(btcAddress: string) {
btcAvailableUsdBalance: i18nFormatCurrency(
baseCurrencyAmountInQuote(btcAssetBalance.balance, btcMarketData)
),
isLoading,
isInitialLoading,
}),
[btcAddress, btcAssetBalance, btcMarketData]
[btcAddress, btcAssetBalance, btcMarketData, isLoading, isInitialLoading]
);
}
27 changes: 19 additions & 8 deletions src/app/common/hooks/balance/use-total-balance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,20 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
const stxMarketData = useCryptoCurrencyMarketData('STX');

// get stx balance
const { data: balances, isLoading } = useStacksAccountBalances(stxAddress);
const { data: balances, isLoading, isInitialLoading } = useStacksAccountBalances(stxAddress);
const stxBalance = balances ? balances.stx.balance : createMoney(0, 'STX');

// get btc balance
const btcBalance = useBtcAssetBalance(btcAddress);
const {
btcAvailableAssetBalance,
isLoading: isLoadingBtcBalance,
isInitialLoading: isInititalLoadingBtcBalance,
} = useBtcAssetBalance(btcAddress);

return useMemo(() => {
// calculate total balance
const stxUsdAmount = baseCurrencyAmountInQuote(stxBalance, stxMarketData);
const btcUsdAmount = baseCurrencyAmountInQuote(
btcBalance.btcAvailableAssetBalance.balance,
btcMarketData
);
const btcUsdAmount = baseCurrencyAmountInQuote(btcAvailableAssetBalance.balance, btcMarketData);

const totalBalance = { ...stxUsdAmount, amount: stxUsdAmount.amount.plus(btcUsdAmount.amount) };
return {
Expand All @@ -41,7 +42,17 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
totalBalance,
totalBalance.amount.isGreaterThanOrEqualTo(100_000) ? 0 : 2
),
isLoading,
isLoading: isLoading || isLoadingBtcBalance,
isInitialLoading: isInitialLoading || isInititalLoadingBtcBalance,
};
}, [btcBalance, btcMarketData, stxMarketData, isLoading, stxBalance]);
}, [
btcAvailableAssetBalance.balance,
btcMarketData,
isInitialLoading,
isInititalLoadingBtcBalance,
stxMarketData,
stxBalance,
isLoading,
isLoadingBtcBalance,
]);
}
20 changes: 15 additions & 5 deletions src/app/components/account-total-balance.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import { memo } from 'react';

import { styled } from 'leather-styles/jsx';
import { css } from 'leather-styles/css';
import { type BoxProps, styled } from 'leather-styles/jsx';

import { useTotalBalance } from '@app/common/hooks/balance/use-total-balance';

import { shimmerStyles } from '../../../theme/global/shimmer-styles';
import { shimmerStyles } from '../../shared/shimmer-styles';
import { BalanceShimmer } from './balance/balance-shimmer';

interface AccountTotalBalanceProps {
interface AccountTotalBalanceProps extends BoxProps {
btcAddress: string;
stxAddress: string;
}

export const AccountTotalBalance = memo(({ btcAddress, stxAddress }: AccountTotalBalanceProps) => {
const { totalUsdBalance, isLoading } = useTotalBalance({ btcAddress, stxAddress });
const { totalUsdBalance, isLoading, isInitialLoading } = useTotalBalance({
btcAddress,
stxAddress,
});

if (!totalUsdBalance) return null;

if (isInitialLoading) {
return <BalanceShimmer height="20px" />;
}

return (
<styled.span
className={css(shimmerStyles)}
fontWeight={500}
textStyle="label.02"
data-state={isLoading ? 'loading' : undefined}
className={shimmerStyles}
>
{totalUsdBalance}
</styled.span>
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/account/account-name.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { memo } from 'react';

import { css } from 'leather-styles/css';
import { styled } from 'leather-styles/jsx';

import { shimmerStyles } from '../../../../theme/global/shimmer-styles';
import { shimmerStyles } from '../../../shared/shimmer-styles';

interface AccountNameLayoutProps {
children: React.ReactNode;
Expand All @@ -11,11 +12,11 @@ interface AccountNameLayoutProps {

export const AccountNameLayout = memo(({ children, isLoading }: AccountNameLayoutProps) => (
<styled.span
className={css(shimmerStyles)}
fontWeight={500}
textStyle="label.02"
aria-busy={isLoading}
data-state={isLoading ? 'loading' : undefined}
className={shimmerStyles}
>
{children}
</styled.span>
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/balance-btc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance
import { Caption } from '@app/ui/components/typography/caption';

export function BtcBalance() {
const balance = useCurrentNativeSegwitAddressBalance();
const { balance } = useCurrentNativeSegwitAddressBalance();

return <Caption>{formatMoney(balance)}</Caption>;
}
19 changes: 19 additions & 0 deletions src/app/components/balance/balance-shimmer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta } from '@storybook/react';

import { BalanceShimmer as Component } from './balance-shimmer';

const meta: Meta<typeof Component> = {
component: Component,
tags: ['autodocs'],
title: 'Layout/BalanceShimmer',
};

export default meta;

export function CryptoAssetTokenBalance() {
return <Component width="126px" />;
}

export function CryptoAssetUsdBalance() {
return <Component width="78px" />;
}
24 changes: 24 additions & 0 deletions src/app/components/balance/balance-shimmer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { css } from 'leather-styles/css';
import { Box } from 'leather-styles/jsx';

export function BalanceShimmer({ ...rest }) {
return (
<Box
width="30px"
height="30px"
bgColor="ink.non-interactive"
data-state="loading"
borderRadius="sm"
className={css({
'&[data-state=loading]': {
display: 'inline-block',
WebkitMask: 'linear-gradient(-60deg, #000 30%, #0005, #000 70%) right/300% 100%',
backgroundRepeat: 'no-repeat',
animation: 'shimmer 1.5s infinite',
color: 'ink.text-subdued',
},
})}
{...rest}
/>
);
}
6 changes: 3 additions & 3 deletions src/app/components/balance/bitcoin-balance-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-se

interface BitcoinBalanceLoaderProps {
address: string;
children(balance: BitcoinCryptoCurrencyAssetBalance): React.ReactNode;
children(balance: BitcoinCryptoCurrencyAssetBalance, isInitialLoading: boolean): React.ReactNode;
}

export function BitcoinBalanceLoader({ address, children }: BitcoinBalanceLoaderProps) {
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(address);
return children(btcCryptoCurrencyAssetBalance);
const { btcBalance, isInitialLoading } = useNativeSegwitBalance(address);
return children(btcBalance, isInitialLoading);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface UseBitcoinCustomFeeArgs {
recipient: string;
}
export function useBitcoinCustomFee({ amount, isSendingMax, recipient }: UseBitcoinCustomFeeArgs) {
const balance = useCurrentNativeSegwitAddressBalance();
const { balance } = useCurrentNativeSegwitAddressBalance();
const { data: utxos = [] } = useCurrentNativeSegwitUtxos();
const btcMarketData = useCryptoCurrencyMarketData('BTC');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function useBitcoinFeesList({
recipient,
utxos,
}: UseBitcoinFeesListArgs) {
const balance = useCurrentNativeSegwitAddressBalance();
const { balance } = useCurrentNativeSegwitAddressBalance();
const btcMarketData = useCryptoCurrencyMarketData('BTC');
const { data: feeRates, isLoading } = useAverageBitcoinFeeRates();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { Brc20TokenAssetItemLayout } from './components/brc20-token-asset-item.l
export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) {
const navigate = useNavigate();
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(currentAccountBtcAddress);
const { btcBalance: btcCryptoCurrencyAssetBalance } =
useNativeSegwitBalance(currentAccountBtcAddress);

const hasPositiveBtcBalanceForFees =
btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ export function CryptoAssetList({
<BitcoinNativeSegwitAccountLoader current>
{signer => (
<BitcoinBalanceLoader address={signer.address}>
{balance => (
{(balance, isLoading) => (
<CryptoCurrencyAssetItemLayout
assetBalance={balance}
icon={<BtcAvatarIcon />}
onClick={() => onItemClick(balance)}
isLoading={isLoading}
/>
)}
</BitcoinBalanceLoader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Box, Flex, styled } from 'leather-styles/jsx';

import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model';

import { BalanceShimmer } from '@app/components/balance/balance-shimmer';
import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator';
import { ItemLayout } from '@app/ui/components/item-layout/item-layout';
import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip';
Expand All @@ -18,6 +19,7 @@ interface CryptoCurrencyAssetItemLayoutProps {
address?: string;
assetBalance: AllCryptoCurrencyAssetBalances;
icon: React.ReactNode;
isLoading?: boolean;
onClick?(): void;
rightElement?: React.ReactNode;
usdBalance?: string;
Expand All @@ -31,44 +33,50 @@ export function CryptoCurrencyAssetItemLayout({
onClick,
rightElement,
usdBalance,
isLoading,
}: CryptoCurrencyAssetItemLayoutProps) {
const { balance, dataTestId, formattedBalance, title } =
parseCryptoCurrencyAssetBalance(assetBalance);

const titleRight = isLoading ? (
<BalanceShimmer width="126px" />
) : rightElement ? (
rightElement
) : (
<BasicTooltip
asChild
label={formattedBalance.isAbbreviated ? balance.amount.toString() : undefined}
side="left"
>
<styled.span data-testid={title} fontWeight={500} textStyle="label.02">
{formattedBalance.value} {additionalBalanceInfo}
</styled.span>
</BasicTooltip>
);

const captionRight = isLoading ? (
<BalanceShimmer width="78px" />
) : rightElement ? (
rightElement
) : (
<Caption>
<Flex alignItems="center" gap="space.02" color="inherit">
<BulletSeparator>
<Caption>{balance.amount.toNumber() > 0 && address ? usdBalance : null}</Caption>
{additionalUsdBalanceInfo}
</BulletSeparator>
</Flex>
</Caption>
);
const isInteractive = !!onClick;

const content = (
<ItemLayout
flagImg={icon}
titleLeft={title}
captionLeft={balance.symbol}
titleRight={
rightElement ? (
rightElement
) : (
<BasicTooltip
asChild
label={formattedBalance.isAbbreviated ? balance.amount.toString() : undefined}
side="left"
>
<styled.span data-testid={title} fontWeight={500} textStyle="label.02">
{formattedBalance.value} {additionalBalanceInfo}
</styled.span>
</BasicTooltip>
)
}
captionRight={
!rightElement && (
<Caption>
<Flex alignItems="center" gap="space.02" color="inherit">
<BulletSeparator>
<Caption>{balance.amount.toNumber() > 0 && address ? usdBalance : null}</Caption>
{additionalUsdBalanceInfo}
</BulletSeparator>
</Flex>
</Caption>
)
}
titleRight={titleRight}
captionRight={captionRight}
/>
);

Expand Down
5 changes: 4 additions & 1 deletion src/app/features/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export function AssetsList() {
const btcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const network = useCurrentNetwork();

const { btcAvailableAssetBalance, btcAvailableUsdBalance } = useBtcAssetBalance(btcAddress);
const { btcAvailableAssetBalance, btcAvailableUsdBalance, isInitialLoading } =
useBtcAssetBalance(btcAddress);

const { whenWallet } = useWalletType();

Expand All @@ -40,6 +41,7 @@ export function AssetsList() {
usdBalance={btcAvailableUsdBalance}
icon={<BtcAvatarIcon />}
address={btcAddress}
isLoading={isInitialLoading}
/>
),
ledger: (
Expand All @@ -48,6 +50,7 @@ export function AssetsList() {
usdBalance={btcAvailableUsdBalance}
icon={<BtcAvatarIcon />}
address={btcAddress}
isLoading={isInitialLoading}
rightElement={
hasBitcoinLedgerKeys ? undefined : <ConnectLedgerAssetBtn chain="bitcoin" />
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/bitcoin-choose-fee/bitcoin-choose-fee.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function BitcoinChooseFee({
...rest
}: BitcoinChooseFeeProps) {
const nativeSegwitSigner = useCurrentAccountNativeSegwitIndexZeroSigner();
const btcBalance = useNativeSegwitBalance(nativeSegwitSigner.address);
const { btcBalance } = useNativeSegwitBalance(nativeSegwitSigner.address);
const hasAmount = amount.amount.isGreaterThan(0);
const [customFeeInitialValue, setCustomFeeInitialValue] = useState(recommendedFeeRate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useCurrentNativeSegwitAddressBalance } from '@app/query/bitcoin/balance

export function useValidateBitcoinSpend(amount?: Money, isSendingMax?: boolean) {
const [showInsufficientBalanceError, setShowInsufficientBalanceError] = useState(false);
const balance = useCurrentNativeSegwitAddressBalance();
const { balance } = useCurrentNativeSegwitAddressBalance();

return {
showInsufficientBalanceError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ function PendingBrcTransfer({ order }: PendingBrcTransferProps) {
const navigate = useNavigate();
const ordinalsbotClient = useOrdinalsbotClient();
const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero();
const btcCryptoCurrencyAssetBalance = useNativeSegwitBalance(currentAccountBtcAddress);
const { btcBalance: btcCryptoCurrencyAssetBalance } =
useNativeSegwitBalance(currentAccountBtcAddress);

const hasPositiveBtcBalanceForFees =
btcCryptoCurrencyAssetBalance.balance.amount.isGreaterThan(0);
Expand Down
Loading

0 comments on commit 53a8b08

Please sign in to comment.