diff --git a/package.json b/package.json index bcb63566d82..e68fb84f09b 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "@leather-wallet/constants": "0.6.3", "@leather-wallet/crypto": "1.0.1", "@leather-wallet/models": "0.8.0", - "@leather-wallet/query": "0.8.6", + "@leather-wallet/query": "0.8.7", "@leather-wallet/tokens": "0.5.2", "@leather-wallet/utils": "0.8.1", "@ledgerhq/hw-transport-webusb": "6.27.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e5ee77f24a4..152f229101f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,8 +47,8 @@ importers: specifier: 0.8.0 version: 0.8.0 '@leather-wallet/query': - specifier: 0.8.6 - version: 0.8.6(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 0.8.7 + version: 0.8.7(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@leather-wallet/tokens': specifier: 0.5.2 version: 0.5.2 @@ -2512,8 +2512,8 @@ packages: '@leather-wallet/prettier-config@0.4.1': resolution: {integrity: sha512-8J1OqfAPtP64MUp3yERQVKnWdp7wHLBjcWlnu68qvS6obxWKkXTZVArHyvADkHRdpmu7rvEzhFRi+fyw00Ui/Q==} - '@leather-wallet/query@0.8.6': - resolution: {integrity: sha512-DmpX32UEirFeqipcDijGvgDbOZkWmF+xUm9yuPtK44h4NJJx+hhcaQkFIfvCLmcTlTvBv42sEbnqeIw3CBDfow==} + '@leather-wallet/query@0.8.7': + resolution: {integrity: sha512-Qn9vEkq+zNEolLZ2MJKO9jmhHnvhBlIf0JjaSe5tlBEy266JTH7IwzS4OdFAf0pP3Ugjf/Cqz5qz0k0Y4EZrFw==} peerDependencies: react: '*' @@ -15503,7 +15503,7 @@ snapshots: - '@vue/compiler-sfc' - supports-color - '@leather-wallet/query@0.8.6(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@leather-wallet/query@0.8.7(@stacks/network@6.13.0(encoding@0.1.13))(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@fungible-systems/zone-file': 2.0.0 '@hirosystems/token-metadata-api-client': 1.2.0(encoding@0.1.13) diff --git a/src/app/common/hooks/balance/use-total-balance.tsx b/src/app/common/hooks/balance/use-total-balance.tsx index eada203e6f7..81d205888b4 100644 --- a/src/app/common/hooks/balance/use-total-balance.tsx +++ b/src/app/common/hooks/balance/use-total-balance.tsx @@ -27,12 +27,8 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) const stxBalance = balance ? balance.totalBalance : createMoney(0, 'STX'); // get btc balance - const { - balance: btcBalance, - isLoading: isLoadingBtcBalance, - isFetching: isFetchingBtcBalance, - isInitialLoading: isInititalLoadingBtcBalance, - } = useBtcCryptoAssetBalanceNativeSegwit(btcAddress); + const { balance: btcBalance, query: btcQueryResult } = + useBtcCryptoAssetBalanceNativeSegwit(btcAddress); return useMemo(() => { // calculate total balance @@ -46,9 +42,9 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) totalBalance, totalBalance.amount.isGreaterThanOrEqualTo(100_000) ? 0 : 2 ), - isLoading: isLoading || isLoadingBtcBalance, - isInitialLoading: isInitialLoading || isInititalLoadingBtcBalance, - isFetching: isFetchingStacksBalance || isFetchingBtcBalance, + isLoading: isLoading || btcQueryResult.isLoading, + isInitialLoading: isInitialLoading || btcQueryResult.isInitialLoading, + isFetching: isFetchingStacksBalance || btcQueryResult.isFetching, }; }, [ stxBalance, @@ -56,10 +52,10 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) btcBalance.availableBalance, btcMarketData, isLoading, - isLoadingBtcBalance, + btcQueryResult.isLoading, + btcQueryResult.isInitialLoading, + btcQueryResult.isFetching, isInitialLoading, - isInititalLoadingBtcBalance, isFetchingStacksBalance, - isFetchingBtcBalance, ]); } diff --git a/src/app/components/crypto-asset-item/crypto-asset-item-error.tsx b/src/app/components/crypto-asset-item/crypto-asset-item-error.tsx new file mode 100644 index 00000000000..e6a42c98e30 --- /dev/null +++ b/src/app/components/crypto-asset-item/crypto-asset-item-error.tsx @@ -0,0 +1,39 @@ +import type { QueryObserverResult } from '@tanstack/react-query'; +import { Box, styled } from 'leather-styles/jsx'; + +import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; + +interface CryptoAssetItemErrorProps { + caption: string; + icon: React.ReactNode; + onRefetch?(): Promise>; + title: string; +} +export function CryptoAssetItemError({ + caption, + icon, + onRefetch, + title, +}: CryptoAssetItemErrorProps) { + return ( + + + Unable to load + + } + captionRight={ + onRefetch && ( + + Retry + + ) + } + /> + + ); +} diff --git a/src/app/components/crypto-asset-item/crypto-asset-item-placeholder.tsx b/src/app/components/crypto-asset-item/crypto-asset-item-placeholder.tsx new file mode 100644 index 00000000000..4254f2d1427 --- /dev/null +++ b/src/app/components/crypto-asset-item/crypto-asset-item-placeholder.tsx @@ -0,0 +1,18 @@ +import { Box, Circle } from 'leather-styles/jsx'; + +import { ItemLayout } from '@app/ui/components/item-layout/item-layout'; +import { SkeletonLoader } from '@app/ui/components/skeleton-loader/skeleton-loader'; + +export function CryptoAssetItemPlaceholder() { + return ( + + } + titleLeft={} + captionLeft={} + titleRight={} + captionRight={} + /> + + ); +} diff --git a/src/app/components/loaders/btc-balance-loader.tsx b/src/app/components/loaders/btc-balance-loader.tsx index a5b3a4e86a8..7ded71407d9 100644 --- a/src/app/components/loaders/btc-balance-loader.tsx +++ b/src/app/components/loaders/btc-balance-loader.tsx @@ -1,12 +1,20 @@ import type { BtcCryptoAssetBalance } from '@leather-wallet/models'; +import { isFetchedWithSuccess, isInitializingData } from '@leather-wallet/query'; import { useBtcCryptoAssetBalanceNativeSegwit } from '@app/query/bitcoin/balance/btc-balance-native-segwit.hooks'; +import { BtcAvatarIcon } from '@app/ui/components/avatar/btc-avatar-icon'; + +import { CryptoAssetItemError } from '../crypto-asset-item/crypto-asset-item-error'; +import { CryptoAssetItemPlaceholder } from '../crypto-asset-item/crypto-asset-item-placeholder'; interface BtcBalanceLoaderProps { address: string; children(balance: BtcCryptoAssetBalance, isInitialLoading: boolean): React.ReactNode; } export function BtcBalanceLoader({ address, children }: BtcBalanceLoaderProps) { - const { balance, isInitialLoading } = useBtcCryptoAssetBalanceNativeSegwit(address); - return children(balance, isInitialLoading); + const { balance, query: result } = useBtcCryptoAssetBalanceNativeSegwit(address); + if (isInitializingData(result)) return ; + if (!isFetchedWithSuccess(result)) + return } title="Bitcoin" />; + return children(balance, result.isInitialLoading); } diff --git a/src/app/components/loaders/stx-balance-loader.tsx b/src/app/components/loaders/stx-balance-loader.tsx index 416aea90090..60880b6221d 100644 --- a/src/app/components/loaders/stx-balance-loader.tsx +++ b/src/app/components/loaders/stx-balance-loader.tsx @@ -1,7 +1,15 @@ import type { StxCryptoAssetBalance } from '@leather-wallet/models'; -import { useStxCryptoAssetBalance } from '@leather-wallet/query'; +import { + isErrorTooManyRequests, + isFetchedWithSuccess, + isInitializingData, + useStxCryptoAssetBalance, +} from '@leather-wallet/query'; -import { isFetchedWithSuccess } from '@app/query/query-config'; +import { StxAvatarIcon } from '@app/ui/components/avatar/stx-avatar-icon'; + +import { CryptoAssetItemError } from '../crypto-asset-item/crypto-asset-item-error'; +import { CryptoAssetItemPlaceholder } from '../crypto-asset-item/crypto-asset-item-placeholder'; interface StxBalanceLoaderProps { address: string; @@ -9,7 +17,18 @@ interface StxBalanceLoaderProps { } export function StxBalanceLoader({ address, children }: StxBalanceLoaderProps) { const result = useStxCryptoAssetBalance(address); - if (!isFetchedWithSuccess(result)) return null; + if (isInitializingData(result)) return ; + if (isErrorTooManyRequests(result)) + return ( + } + onRefetch={() => result.refetch()} + title="Stacks" + /> + ); + if (!isFetchedWithSuccess(result)) + return } title="Stacks" />; const { data: balance, isInitialLoading } = result; return children(balance, isInitialLoading); } diff --git a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx index 3d7abccc32b..d98229891dc 100644 --- a/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ b/src/app/features/asset-list/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -25,7 +25,7 @@ interface Brc20TokenAssetListProps { } export function Brc20TokenAssetList({ tokens, variant }: Brc20TokenAssetListProps) { const navigate = useNavigate(); - const { balance, isInitialLoading } = useCurrentBtcCryptoAssetBalanceNativeSegwit(); + const { balance, query: result } = useCurrentBtcCryptoAssetBalanceNativeSegwit(); const hasPositiveBtcBalanceForFees = variant === 'interactive' && balance.availableBalance.amount.isGreaterThan(0); @@ -49,7 +49,7 @@ export function Brc20TokenAssetList({ tokens, variant }: Brc20TokenAssetListProp availableBalance={token.balance.availableBalance} captionLeft={token.info.name.toUpperCase()} icon={} - isLoading={isInitialLoading} + isLoading={result.isInitialLoading} key={token.info.symbol} onSelectAsset={ hasPositiveBtcBalanceForFees ? () => navigateToBrc20SendForm(token) : undefined diff --git a/src/app/features/collectibles/hooks/use-is-fetching-collectibles.ts b/src/app/features/collectibles/hooks/use-is-fetching-collectibles.ts index dfa4ece8243..f21370afe7b 100644 --- a/src/app/features/collectibles/hooks/use-is-fetching-collectibles.ts +++ b/src/app/features/collectibles/hooks/use-is-fetching-collectibles.ts @@ -1,9 +1,8 @@ import { useIsFetching } from '@tanstack/react-query'; +import { QueryPrefixes } from '@leather-wallet/query'; import { sumNumbers } from '@leather-wallet/utils'; -import { QueryPrefixes } from '@app/query/query-prefixes'; - function areAnyQueriesFetching(...args: number[]) { return sumNumbers(args).toNumber() > 0; } diff --git a/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts b/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts index bf0a5612486..18d0dfd8c04 100644 --- a/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts +++ b/src/app/query/bitcoin/balance/btc-balance-native-segwit.hooks.ts @@ -20,12 +20,7 @@ function createBtcCryptoAssetBalance(balance: Money): BtcCryptoAssetBalance { export function useBtcCryptoAssetBalanceNativeSegwit(address: string) { const runesEnabled = useRunesEnabled(); - const { - data: utxos, - isInitialLoading, - isLoading, - isFetching, - } = useNativeSegwitUtxosByAddress({ + const query = useNativeSegwitUtxosByAddress({ address, filterInscriptionUtxos: true, filterPendingTxsUtxos: true, @@ -33,18 +28,16 @@ export function useBtcCryptoAssetBalanceNativeSegwit(address: string) { }); const balance = useMemo(() => { - if (isUndefined(utxos)) + if (isUndefined(query.data)) return createBtcCryptoAssetBalance(createMoney(new BigNumber(0), 'BTC')); return createBtcCryptoAssetBalance( - createMoney(sumNumbers(utxos.map(utxo => utxo.value)), 'BTC') + createMoney(sumNumbers(query.data.map(utxo => utxo.value)), 'BTC') ); - }, [utxos]); + }, [query.data]); return { balance, - isInitialLoading, - isLoading, - isFetching, + query, }; } diff --git a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts index 9f886d4ba22..54a0f479d6b 100644 --- a/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts +++ b/src/app/query/bitcoin/ordinals/brc20/brc20-tokens.hooks.ts @@ -9,6 +9,7 @@ import { import { createBrc20TransferInscription, encodeBrc20TransferInscription, + isFetchedWithSuccess, useAverageBitcoinFeeRates, useCalculateBitcoinFiatValue, useConfigOrdinalsbot, @@ -17,7 +18,6 @@ import { } from '@leather-wallet/query'; import { createMoney, unitToFractionalUnit } from '@leather-wallet/utils'; -import { isFetchedWithSuccess } from '@app/query/query-config'; import { useAppDispatch } from '@app/store'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useCurrentAccountNativeSegwitIndexZeroSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; diff --git a/src/app/query/query-config.ts b/src/app/query/query-config.ts deleted file mode 100644 index 5e8cc86808e..00000000000 --- a/src/app/query/query-config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type QueryObserverSuccessResult, type UseQueryResult } from '@tanstack/react-query'; - -// TODO #40: Import from query pkg and remove -export function isFetchedWithSuccess( - query: UseQueryResult -): query is QueryObserverSuccessResult { - return !query.isError && !query.isLoading && query.data !== undefined; -} diff --git a/src/app/query/query-prefixes.ts b/src/app/query/query-prefixes.ts deleted file mode 100644 index bf0602cb54a..00000000000 --- a/src/app/query/query-prefixes.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * These values are used as the first item in some query keys, allowing the - * queries they are used in to be read from any part of the app. - */ -export enum QueryPrefixes { - Brc20TokenBalance = 'brc20-token-balance', - OrdinalTextContent = 'ordinal-text-content', - TaprootAddressUtxos = 'taproot-address-utxos', - BnsNamesByAddress = 'bns-names-by-address', - InscriptionsByAddress = 'inscriptions-by-address', - InscriptionMetadata = 'inscription-metadata', - GetInscriptions = 'get-inscriptions', - GetNftMetadata = 'get-nft-metadata', - GetNftHoldings = 'get-nft-holdings', - - StampCollection = 'stamp-collection', - StampsByAddress = 'stamps-by-address', - GetBrc20Tokens = 'get-brc20-tokens', -} diff --git a/src/app/ui/components/skeleton-loader/skeleton-loader.tsx b/src/app/ui/components/skeleton-loader/skeleton-loader.tsx index 3d4475d6165..268d72a7b31 100644 --- a/src/app/ui/components/skeleton-loader/skeleton-loader.tsx +++ b/src/app/ui/components/skeleton-loader/skeleton-loader.tsx @@ -8,7 +8,6 @@ interface SkeletonLoaderProps { width?: string; height?: string; } - export function SkeletonLoader({ children, width = '30px',