diff --git a/package.json b/package.json index f6e028700bc..fa0e57db5e5 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "@dlc-link/dlc-tools": "1.1.1", "@fungible-systems/zone-file": "2.0.0", "@hirosystems/token-metadata-api-client": "1.2.0", - "@leather-wallet/tokens": "0.0.5", + "@leather-wallet/tokens": "0.0.6", "@ledgerhq/hw-transport-webusb": "6.27.19", "@noble/hashes": "1.3.2", "@noble/secp256k1": "2.0.0", @@ -319,7 +319,7 @@ "react-refresh": "0.14.0", "schema-inspector": "2.0.2", "speed-measure-webpack-plugin": "1.5.0", - "storybook": "7.6.10", + "storybook": "7.6.12", "stream-browserify": "3.0.0", "svg-url-loader": "8.0.0", "ts-node": "10.9.2", diff --git a/src/app/common/hooks/analytics/use-track-switch-account.ts b/src/app/common/hooks/analytics/use-track-switch-account.ts index 10ef8a524d9..cb3f70ad2f4 100644 --- a/src/app/common/hooks/analytics/use-track-switch-account.ts +++ b/src/app/common/hooks/analytics/use-track-switch-account.ts @@ -10,10 +10,7 @@ export function useTrackSwitchAccount() { return useCallback( async (address: string, index: number) => { - const accountBalanceCache = queryClient.getQueryData([ - 'get-address-anchored-stx-balance', - address, - ]); + const accountBalanceCache = queryClient.getQueryData(['get-address-stx-balance', address]); if (!accountBalanceCache) return; try { const balances = parseBalanceResponse(accountBalanceCache as any); diff --git a/src/app/common/hooks/balance/stx/use-stx-balance.ts b/src/app/common/hooks/balance/stx/use-stx-balance.ts index 2f9efb8a305..5a7312902f7 100644 --- a/src/app/common/hooks/balance/stx/use-stx-balance.ts +++ b/src/app/common/hooks/balance/stx/use-stx-balance.ts @@ -7,38 +7,36 @@ import { baseCurrencyAmountInQuote, subtractMoney } from '@app/common/money/calc import { i18nFormatCurrency } from '@app/common/money/format-money'; import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; -import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks'; - -import { useStxOutboundValue } from './use-stx-outbound-value'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks'; export function useStxBalance() { - const anchoredBalanceQuery = useCurrentStacksAccountAnchoredBalances(); - const totalBalance = anchoredBalanceQuery.data?.stx.balance; - const unlockedStxBalance = anchoredBalanceQuery.data?.stx.unlockedStx; + const stxBalanceQuery = useCurrentStacksAccountBalances(); + const totalBalance = stxBalanceQuery.data?.stx.balance; + const unlockedStxBalance = stxBalanceQuery.data?.stx.unlockedStx; const stxMarketData = useCryptoCurrencyMarketData('STX'); - const stxOutboundQuery = useStxOutboundValue(); + const pendingTxsBalance = useCurrentAccountMempoolTransactionsBalance(); const stxEffectiveBalance = isDefined(totalBalance) - ? subtractMoney(totalBalance, stxOutboundQuery.data) + ? subtractMoney(totalBalance, pendingTxsBalance) : createMoney(0, 'STX'); const stxEffectiveUsdBalance = isDefined(totalBalance) ? i18nFormatCurrency(baseCurrencyAmountInQuote(stxEffectiveBalance, stxMarketData)) : undefined; - const stxLockedBalance = anchoredBalanceQuery.data?.stx.locked; + const stxLockedBalance = stxBalanceQuery.data?.stx.locked; const stxUsdLockedBalance = isDefined(stxLockedBalance) ? i18nFormatCurrency(baseCurrencyAmountInQuote(stxLockedBalance, stxMarketData)) : undefined; return useMemo(() => { return { - anchoredBalanceQuery, - stxOutboundQuery, - isLoading: anchoredBalanceQuery.isLoading || stxOutboundQuery.isLoading, + stxBalanceQuery, + stxOutboundQuery: pendingTxsBalance, availableBalance: isDefined(unlockedStxBalance) - ? subtractMoney(unlockedStxBalance, stxOutboundQuery.data) + ? subtractMoney(unlockedStxBalance, pendingTxsBalance) : createMoney(0, 'STX'), stxEffectiveBalance: createStacksCryptoCurrencyAssetTypeWrapper(stxEffectiveBalance.amount), stxEffectiveUsdBalance, @@ -46,10 +44,10 @@ export function useStxBalance() { stxUsdLockedBalance, }; }, [ - anchoredBalanceQuery, - stxOutboundQuery, + stxBalanceQuery, + pendingTxsBalance, unlockedStxBalance, - stxEffectiveBalance, + stxEffectiveBalance.amount, stxEffectiveUsdBalance, stxLockedBalance, stxUsdLockedBalance, diff --git a/src/app/common/hooks/balance/stx/use-stx-outbound-value.ts b/src/app/common/hooks/balance/stx/use-stx-outbound-value.ts deleted file mode 100644 index 7d5ae4a104e..00000000000 --- a/src/app/common/hooks/balance/stx/use-stx-outbound-value.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { sumMoney } from '@app/common/money/calculate-money'; -import { useCurrentAccountMempoolTransactionsBalance } from '@app/query/stacks/mempool/mempool.hooks'; -import { useCurrentAccountMicroblockBalanceQuery } from '@app/query/stacks/microblock/microblock.query'; -import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; - -export function useStxOutboundValue() { - const stxAddress = useCurrentAccountStxAddressState(); - const pendingTxsBalance = useCurrentAccountMempoolTransactionsBalance(); - const microblockBalanceQuery = useCurrentAccountMicroblockBalanceQuery(stxAddress); - - if (!microblockBalanceQuery.data) { - return { ...microblockBalanceQuery, data: pendingTxsBalance }; - } - - return { - ...microblockBalanceQuery, - data: sumMoney([pendingTxsBalance, microblockBalanceQuery.data]), - }; -} diff --git a/src/app/common/hooks/balance/use-total-balance.tsx b/src/app/common/hooks/balance/use-total-balance.tsx index 27aa0b54875..e1c07fbdc86 100644 --- a/src/app/common/hooks/balance/use-total-balance.tsx +++ b/src/app/common/hooks/balance/use-total-balance.tsx @@ -5,7 +5,7 @@ import { createMoney } from '@shared/models/money.model'; import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; -import { useAnchoredStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useBtcAssetBalance } from './btc/use-btc-balance'; @@ -20,7 +20,7 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs) const stxMarketData = useCryptoCurrencyMarketData('STX'); // get stx balance - const { data: balances, isLoading } = useAnchoredStacksAccountBalances(stxAddress); + const { data: balances, isLoading } = useStacksAccountBalances(stxAddress); const stxBalance = balances ? balances.stx.balance : createMoney(0, 'STX'); // get btc balance diff --git a/src/app/common/hooks/use-key-actions.ts b/src/app/common/hooks/use-key-actions.ts index 4dc44a80e46..27fb30289e1 100644 --- a/src/app/common/hooks/use-key-actions.ts +++ b/src/app/common/hooks/use-key-actions.ts @@ -9,7 +9,7 @@ import { queryClient } from '@app/common/persistence'; import { partiallyClearLocalStorage } from '@app/common/store-utils'; import { useAppDispatch } from '@app/store'; import { createNewAccount, stxChainActions } from '@app/store/chains/stx-chain.actions'; -import { useBitcoinClient, useStacksClientAnchored } from '@app/store/common/api-clients.hooks'; +import { useBitcoinClient, useStacksClient } from '@app/store/common/api-clients.hooks'; import { inMemoryKeyActions } from '@app/store/in-memory-key/in-memory-key.actions'; import { bitcoinKeysSlice } from '@app/store/ledger/bitcoin/bitcoin-key.slice'; import { stacksKeysSlice } from '@app/store/ledger/stacks/stacks-key.slice'; @@ -23,7 +23,7 @@ export function useKeyActions() { const analytics = useAnalytics(); const dispatch = useAppDispatch(); const defaultKeyDetails = useCurrentKeyDetails(); - const stxClient = useStacksClientAnchored(); + const stxClient = useStacksClient(); const btcClient = useBitcoinClient(); return useMemo( diff --git a/src/app/common/hooks/use-submit-stx-transaction.ts b/src/app/common/hooks/use-submit-stx-transaction.ts index 56de95a7992..7f64bd24286 100644 --- a/src/app/common/hooks/use-submit-stx-transaction.ts +++ b/src/app/common/hooks/use-submit-stx-transaction.ts @@ -5,6 +5,7 @@ import { bytesToHex } from '@stacks/common'; import { StacksTransaction, broadcastTransaction } from '@stacks/transactions'; import { logger } from '@shared/logger'; +import { isError } from '@shared/utils'; import { getErrorMessage } from '@app/common/get-error-message'; import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh-all-account-data'; @@ -59,7 +60,7 @@ export function useSubmitTransactionCallback({ loadingKey }: UseSubmitTransactio } } catch (error) { logger.error('Transaction callback', { error }); - onError(error instanceof Error ? error : { name: '', message: '' }); + onError(isError(error) ? error : { name: '', message: '' }); setIsIdle(); } }, diff --git a/src/app/common/transactions/stacks/transaction.utils.ts b/src/app/common/transactions/stacks/transaction.utils.ts index 068daeac07b..a4aab96e1bb 100644 --- a/src/app/common/transactions/stacks/transaction.utils.ts +++ b/src/app/common/transactions/stacks/transaction.utils.ts @@ -25,10 +25,7 @@ import { truncateMiddle } from '@app/ui/utils/truncate-middle'; export const statusFromTx = (tx: StacksTx): StacksTxStatus => { const { tx_status } = tx; if (tx_status === 'pending') return 'pending'; - if (tx_status === 'success') - return 'is_unanchored' in tx && tx.is_unanchored - ? 'success_microblock' - : 'success_anchor_block'; + if (tx_status === 'success') return 'success'; return 'failed'; }; diff --git a/src/app/common/utils/safe-await.ts b/src/app/common/utils/safe-await.ts index 304aff22a0c..e0bcec9c54c 100644 --- a/src/app/common/utils/safe-await.ts +++ b/src/app/common/utils/safe-await.ts @@ -1,4 +1,5 @@ // TypeScript port of https://github.com/DavidWells/safe-await/ +import { isError } from '@shared/utils'; // Native Error types https://mzl.la/2Veh3TR const nativeExceptions = [ @@ -19,7 +20,7 @@ function throwNative(error: Error) { export async function safeAwait(promise: Promise, finallyFn?: () => void) { return promise .then(data => { - if (data instanceof Error) { + if (isError(data)) { throwNative(data); return [data] as readonly [Error]; } diff --git a/src/app/components/account-total-balance.tsx b/src/app/components/account-total-balance.tsx index 4dfb1a5622f..efb430ded57 100644 --- a/src/app/components/account-total-balance.tsx +++ b/src/app/components/account-total-balance.tsx @@ -13,5 +13,9 @@ export const AccountTotalBalance = memo(({ btcAddress, stxAddress }: AccountTota if (!totalBalance) return null; - return {totalBalance.totalUsdBalance}; + return ( + + {totalBalance.totalUsdBalance} + + ); }); diff --git a/src/app/components/account/account-addresses.tsx b/src/app/components/account/account-addresses.tsx new file mode 100644 index 00000000000..f9c3c66b64e --- /dev/null +++ b/src/app/components/account/account-addresses.tsx @@ -0,0 +1,29 @@ +import { HStack } from 'leather-styles/jsx'; + +import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; +import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; +import { Caption } from '@app/ui/components/typography/caption'; +import { truncateMiddle } from '@app/ui/utils/truncate-middle'; + +import { StacksAccountLoader } from '../loaders/stacks-account-loader'; +import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader'; + +interface AccountAddressesProps { + index: number; +} +export function AcccountAddresses({ index }: AccountAddressesProps) { + const isBreakpointSm = useViewportMinWidth('sm'); + + return ( + + + + {account => {truncateMiddle(account.address, isBreakpointSm ? 4 : 3)}} + + + {signer => {truncateMiddle(signer.address, 5)}} + + + + ); +} diff --git a/src/app/components/account/account-list-item-layout.tsx b/src/app/components/account/account-list-item-layout.tsx deleted file mode 100644 index 5fde1bb6215..00000000000 --- a/src/app/components/account/account-list-item-layout.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { SettingsSelectors } from '@tests/selectors/settings.selectors'; -import { Flex, HStack, Stack, StackProps, styled } from 'leather-styles/jsx'; - -import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; -import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; -import { CheckmarkIcon } from '@app/ui/components/icons/checkmark-icon'; -import { Spinner } from '@app/ui/components/spinner'; -import { truncateMiddle } from '@app/ui/utils/truncate-middle'; - -import { Flag } from '../layout/flag'; -import { StacksAccountLoader } from '../loaders/stacks-account-loader'; -import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader'; - -interface AccountListItemLayoutProps extends StackProps { - isLoading: boolean; - isActive: boolean; - index: number; - accountName: React.ReactNode; - avatar: React.JSX.Element; - balanceLabel: React.ReactNode; - onSelectAccount(): void; -} -export function AccountListItemLayout(props: AccountListItemLayoutProps) { - const { - index, - isLoading, - isActive, - accountName, - avatar, - balanceLabel, - onSelectAccount, - children = null, - ...rest - } = props; - - const isBreakpointSm = useViewportMinWidth('sm'); - - return ( - - - - - - {accountName} - {isActive && } - - {isLoading ? ( - - ) : ( - balanceLabel - )} - - - - - {account => ( - - {truncateMiddle(account.address, isBreakpointSm ? 4 : 3)} - - )} - - - - {signer => ( - - {truncateMiddle(signer.address, 5)} - - )} - - - - - - {children} - - ); -} diff --git a/src/app/components/account/account-list-item.layout.tsx b/src/app/components/account/account-list-item.layout.tsx new file mode 100644 index 00000000000..f353437b7ec --- /dev/null +++ b/src/app/components/account/account-list-item.layout.tsx @@ -0,0 +1,58 @@ +import { ReactNode } from 'react'; + +import { SettingsSelectors } from '@tests/selectors/settings.selectors'; + +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; +import { Spinner } from '@app/ui/components/spinner'; + +interface AccountListItemLayoutProps { + accountAddresses: ReactNode; + accountName: ReactNode; + avatar: ReactNode; + balanceLabel: ReactNode; + index: number; + isLoading: boolean; + isSelected: boolean; + onSelectAccount(): void; +} +export function AccountListItemLayout(props: AccountListItemLayoutProps) { + const { + accountAddresses, + accountName, + avatar, + balanceLabel, + index, + isLoading, + isSelected, + onSelectAccount, + } = props; + + return ( + + + ) : ( + balanceLabel + ) + } + captionLeft={accountAddresses} + /> + + ); +} diff --git a/src/app/components/account/account-name.tsx b/src/app/components/account/account-name.tsx index 67a07d4fdbf..7101f5d76d5 100644 --- a/src/app/components/account/account-name.tsx +++ b/src/app/components/account/account-name.tsx @@ -6,5 +6,7 @@ interface AccountNameLayoutProps { children: React.ReactNode; } export const AccountNameLayout = memo(({ children }: AccountNameLayoutProps) => ( - {children} + + {children} + )); diff --git a/src/app/components/balance-stx.tsx b/src/app/components/balance-stx.tsx index 65418c5c6b8..bf72d248603 100644 --- a/src/app/components/balance-stx.tsx +++ b/src/app/components/balance-stx.tsx @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { stacksValue } from '@app/common/stacks-utils'; -import { useAnchoredStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { Caption } from '@app/ui/components/typography/caption'; interface BalanceProps { @@ -9,7 +9,7 @@ interface BalanceProps { } export function StxBalance(props: BalanceProps) { const { address } = props; - const { data: balances } = useAnchoredStacksAccountBalances(address); + const { data: balances } = useStacksAccountBalances(address); const balance = useMemo( () => diff --git a/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx b/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx index 32d5b3330e2..08c681f7c0d 100644 --- a/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx +++ b/src/app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point-layout.tsx @@ -4,7 +4,7 @@ import { Money } from '@shared/models/money.model'; import { formatBalance } from '@app/common/format-balance'; import { ftDecimals } from '@app/common/stacks-utils'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; import { LoadingSpinner } from '../loading-spinner'; @@ -30,7 +30,7 @@ export function BitcoinContractEntryPointLayout(props: BitcoinContractEntryPoint return ( - + Bitcoin Contracts - {children} - - ); -} diff --git a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx index 227990cc93f..38e4069dcb7 100644 --- a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx +++ b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-item.tsx @@ -1,8 +1,6 @@ import { useMemo } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { BoxProps } from 'leather-styles/jsx'; - import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; import { RouteUrls } from '@shared/route-urls'; @@ -15,7 +13,6 @@ import { isBitcoinTxInbound, } from '@app/common/transactions/bitcoin/utils'; import { openInNewTab } from '@app/common/utils/open-in-new-tab'; -import { usePressable } from '@app/components/item-hover'; import { IncreaseFeeButton } from '@app/components/stacks-transaction-item/increase-fee-button'; import { TransactionTitle } from '@app/components/transaction/transaction-title'; import { @@ -26,19 +23,17 @@ import { useGetInscriptionsByOutputQuery } from '@app/query/bitcoin/ordinals/ins import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; import { BtcIcon } from '@app/ui/components/icons/btc-icon'; +import { Caption } from '@app/ui/components/typography/caption'; import { TransactionItemLayout } from '../transaction-item/transaction-item.layout'; -import { BitcoinTransactionCaption } from './bitcoin-transaction-caption'; import { BitcoinTransactionIcon } from './bitcoin-transaction-icon'; import { InscriptionIcon } from './bitcoin-transaction-inscription-icon'; import { BitcoinTransactionStatus } from './bitcoin-transaction-status'; -import { BitcoinTransactionValue } from './bitcoin-transaction-value'; -interface BitcoinTransactionItemProps extends BoxProps { +interface BitcoinTransactionItemProps { transaction: BitcoinTx; } -export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransactionItemProps) { - const [component, bind, { isHovered }] = usePressable(true); +export function BitcoinTransactionItem({ transaction }: BitcoinTransactionItemProps) { const { pathname } = useLocation(); const navigate = useNavigate(); @@ -80,19 +75,15 @@ export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransact const txCaption = ( - {caption} - {inscriptionData ? ( - {inscriptionData.mime_type} - ) : null} + {caption} + {inscriptionData ? {inscriptionData.mime_type} : null} ); - const txValue = {value}; const title = inscriptionData ? `Ordinal inscription #${inscriptionData.number}` : 'Bitcoin'; const increaseFeeButton = ( @@ -101,6 +92,7 @@ export function BitcoinTransactionItem({ transaction, ...rest }: BitcoinTransact return ( } txTitle={} - txValue={txValue} - belowCaptionEl={increaseFeeButton} - {...bind} - {...rest} - > - {component} - + txValue={value} + /> ); } diff --git a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-status.tsx b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-status.tsx index 7cbc54caa5c..d0c76a41461 100644 --- a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-status.tsx +++ b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-status.tsx @@ -1,6 +1,7 @@ import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; -import { PendingLabel } from '@app/components/transaction/pending-label'; +import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; +import { Caption } from '@app/ui/components/typography/caption'; interface BitcoinTransactionStatusProps { transaction: BitcoinTx; @@ -10,5 +11,9 @@ const pendingWaitingMessage = export function BitcoinTransactionStatus({ transaction }: BitcoinTransactionStatusProps) { const isPending = !transaction.status.confirmed; - return isPending ? : null; + return isPending ? ( + + Pending + + ) : null; } diff --git a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-value.tsx b/src/app/components/bitcoin-transaction-item/bitcoin-transaction-value.tsx deleted file mode 100644 index 71b9e77eb02..00000000000 --- a/src/app/components/bitcoin-transaction-item/bitcoin-transaction-value.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Title } from '@app/ui/components/typography/title'; - -interface BitcoinTransactionValueProps { - children: string; -} -export function BitcoinTransactionValue({ children }: BitcoinTransactionValueProps) { - return {children}; -} diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx index 5bd77800863..80e06aa8328 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list.tsx @@ -3,11 +3,13 @@ import { useNavigate } from 'react-router-dom'; import { RouteUrls } from '@shared/route-urls'; import { noop } from '@shared/utils'; -import { Brc20TokenAssetItem } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item'; import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; +import { Brc20TokenAssetItemLayout } from './components/brc20-token-asset-item.layout'; +import { Brc20AssetListLayout } from './components/brc20-token-asset-list.layout'; + export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { const navigate = useNavigate(); const currentAccountBtcAddress = useCurrentAccountNativeSegwitAddressIndexZero(); @@ -24,13 +26,17 @@ export function Brc20TokenAssetList(props: { brc20Tokens?: Brc20Token[] }) { } if (!props.brc20Tokens?.length) return null; - return props.brc20Tokens.map(token => ( - navigateToBrc20SendForm(token) : noop} - displayNotEnoughBalance={!hasPositiveBtcBalanceForFees} - key={token.tick} - /> - )); + + return ( + + {props.brc20Tokens?.map(token => ( + navigateToBrc20SendForm(token) : noop} + /> + ))} + + ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx index 0e9223ec493..6232995762b 100644 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout.tsx @@ -1,69 +1,52 @@ -import { BoxProps, Flex, HStack, styled } from 'leather-styles/jsx'; +import { styled } from 'leather-styles/jsx'; -import type { Money } from '@shared/models/money.model'; +import { createMoney } from '@shared/models/money.model'; import { formatBalance } from '@app/common/format-balance'; -import { AssetCaption } from '@app/components/crypto-assets/components/asset-caption'; -import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; +import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; import { Brc20TokenIcon } from '@app/ui/components/icons/brc20-token-icon'; +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -interface Brc20TokenAssetItemLayoutProps extends BoxProps { - balance: Money; - caption: string; - isPressable?: boolean; +interface Brc20TokenAssetItemLayoutProps { + token: Brc20Token; onClick?(): void; - title: string; displayNotEnoughBalance?: boolean; } export function Brc20TokenAssetItemLayout({ - balance, - caption, - isPressable, onClick, - title, displayNotEnoughBalance, + token, }: Brc20TokenAssetItemLayoutProps) { - const [component, bind] = usePressable(isPressable); - + const balance = createMoney(Number(token.overall_balance), token.tick, 0); const formattedBalance = formatBalance(balance.amount.toString()); return ( - - } spacing="space.04" width="100%"> - - - {title} - + + } + titleLeft={token.tick} + captionLeft="BRC-20" + titleRight={ - + {formattedBalance.value} - - - - - {component} - - + } + /> + ); } diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.tsx deleted file mode 100644 index 29820b11099..00000000000 --- a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createMoney } from '@shared/models/money.model'; - -import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; - -import { Brc20TokenAssetItemLayout } from './brc20-token-asset-item.layout'; - -interface Brc20TokenAssetItemProps { - token: Brc20Token; - isPressable?: boolean; - onClick?(): void; - displayNotEnoughBalance?: boolean; -} -export function Brc20TokenAssetItem({ - token, - isPressable, - onClick, - displayNotEnoughBalance, -}: Brc20TokenAssetItemProps) { - return ( - - ); -} diff --git a/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx new file mode 100644 index 00000000000..41ea4db9c38 --- /dev/null +++ b/src/app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-list.layout.tsx @@ -0,0 +1,10 @@ +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; +import { Stack, StackProps } from 'leather-styles/jsx'; + +export function Brc20AssetListLayout({ children }: StackProps) { + return ( + + {children} + + ); +} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx index e98f899f166..4e790a4e35f 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list-item.tsx @@ -1,7 +1,6 @@ import type { AllTransferableCryptoAssetBalances } from '@shared/models/crypto-asset-balance.model'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; - +import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; import { CryptoCurrencyAssetIcon } from './crypto-currency-asset-icon'; import { FungibleTokenAssetItem } from './fungible-token-asset-item'; @@ -16,10 +15,9 @@ export function CryptoAssetListItem(props: CryptoAssetListItemProps) { switch (type) { case 'crypto-currency': return ( - } - isPressable onClick={onClick} /> ); diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx index e1803d73421..c5c9418347b 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.layout.tsx @@ -3,12 +3,7 @@ import { Stack, StackProps } from 'leather-styles/jsx'; export function CryptoAssetListLayout({ children }: StackProps) { return ( - + {children} ); diff --git a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx index fd1fb2f0e0f..0605e67c3bf 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/crypto-asset-list.tsx @@ -8,7 +8,7 @@ import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader'; import { Brc20TokenAssetList } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/brc20-token-asset-list'; import { BtcIcon } from '@app/ui/components/icons/btc-icon'; -import { CryptoCurrencyAssetItem } from '../crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '../crypto-currency-asset/crypto-currency-asset-item.layout'; import { CryptoAssetListItem } from './crypto-asset-list-item'; import { CryptoAssetListLayout } from './crypto-asset-list.layout'; @@ -25,11 +25,10 @@ export function CryptoAssetList({ cryptoAssetBalances, onItemClick }: CryptoAsse {signer => ( {balance => ( - } onClick={() => onItemClick(balance)} - isPressable /> )} diff --git a/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx b/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx index 0ce6b98c4ff..7be8034c33e 100644 --- a/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx +++ b/src/app/components/crypto-assets/choose-crypto-asset/fungible-token-asset-item.tsx @@ -2,7 +2,7 @@ import { FlexProps } from 'leather-styles/jsx'; import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import { StacksFungibleTokenAssetItem } from '@app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item'; +import { StacksFungibleTokenAssetItemLayout } from '../stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout'; interface FungibleTokenAssetItemProps extends FlexProps { assetBalance: StacksFungibleTokenAssetBalance; @@ -13,9 +13,7 @@ export function FungibleTokenAssetItem({ assetBalance, onClick }: FungibleTokenA switch (blockchain) { case 'stacks': - return ( - - ); + return ; default: return null; } diff --git a/src/app/components/crypto-assets/components/asset-caption.tsx b/src/app/components/crypto-assets/components/asset-caption.tsx deleted file mode 100644 index 25116dfecf0..00000000000 --- a/src/app/components/crypto-assets/components/asset-caption.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Flex, HStack, styled } from 'leather-styles/jsx'; - -import { BulletOperator } from '@app/ui/components/bullet-separator/bullet-separator'; -import { InfoIcon } from '@app/ui/components/icons/info-icon'; -import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; - -interface AssetCaptionProps { - caption: string; - isUnanchored?: boolean; -} -export function AssetCaption({ caption, isUnanchored }: AssetCaptionProps) { - return ( - - {caption}{' '} - {isUnanchored ? ( - <> - - Microblock - - - - - - - - - - ) : ( - '' - )} - - ); -} diff --git a/src/app/components/crypto-assets/components/asset-row-grid.tsx b/src/app/components/crypto-assets/components/asset-row-grid.tsx deleted file mode 100644 index 3508934ab32..00000000000 --- a/src/app/components/crypto-assets/components/asset-row-grid.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Grid, GridItem } from 'leather-styles/jsx'; - -interface AssetRowGridProps { - title: React.ReactNode; - balance: React.ReactNode; - caption: React.ReactNode; - usdBalance?: React.ReactNode; - rightElement?: React.ReactNode; -} -export function AssetRowGrid({ - title, - balance, - caption, - usdBalance, - rightElement, -}: AssetRowGridProps) { - const balanceItem = rightElement ? ( - - {rightElement} - - ) : ( - {balance} - ); - return ( - - - {title} - - {balanceItem} - {caption} - {usdBalance && {usdBalance}} - - ); -} diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx index bf4a65215d5..0efb73b7727 100644 --- a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout.tsx @@ -1,103 +1,69 @@ -import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { Flex, styled } from 'leather-styles/jsx'; +import { ReactNode } from 'react'; -import { CryptoCurrencies } from '@shared/models/currencies.model'; -import { Money } from '@shared/models/money.model'; +import { styled } from 'leather-styles/jsx'; -import { formatBalance } from '@app/common/format-balance'; -import { ftDecimals } from '@app/common/stacks-utils'; -import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; +import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; + +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { truncateMiddle } from '@app/ui/utils/truncate-middle'; +import { Caption } from '@app/ui/components/typography/caption'; -import { AssetRowGrid } from '../components/asset-row-grid'; +import { parseCryptoCurrencyAssetBalance } from './crypto-currency-asset.utils'; interface CryptoCurrencyAssetItemLayoutProps { - balance: Money; - caption: string; - icon: React.ReactNode; - copyIcon?: React.ReactNode; - isPressable?: boolean; - title: string; - usdBalance?: string; + additionalBalanceInfo?: ReactNode; + additionalUsdBalanceInfo?: ReactNode; address?: string; - isHovered?: boolean; - currency?: CryptoCurrencies; - additionalBalanceInfo?: React.ReactNode; - additionalUsdBalanceInfo?: React.ReactNode; - rightElement?: React.ReactNode; + assetBalance: AllCryptoCurrencyAssetBalances; + icon: React.ReactNode; onClick?(): void; + rightElement?: React.ReactNode; + usdBalance?: string; } export function CryptoCurrencyAssetItemLayout({ - balance, - caption, - icon, - copyIcon, - isPressable, - title, - usdBalance, - address = '', - isHovered = false, additionalBalanceInfo, additionalUsdBalanceInfo, - rightElement, + address = '', + assetBalance, + icon, onClick, + rightElement, + usdBalance, }: CryptoCurrencyAssetItemLayoutProps) { - const [component, bind] = usePressable(isPressable); - - const amount = balance.decimals - ? ftDecimals(balance.amount, balance.decimals) - : balance.amount.toString(); - const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace( - '{symbol}', - balance.symbol.toLowerCase() - ); - const formattedBalance = formatBalance(amount); + const { balance, dataTestId, formattedBalance, title } = + parseCryptoCurrencyAssetBalance(assetBalance); return ( - - - - {isHovered ? truncateMiddle(address, 6) : title} - - } - balance={ + + - + {formattedBalance.value} {additionalBalanceInfo} - } - caption={ - - {caption} - - } - usdBalance={ - - {balance.amount.toNumber() > 0 && address ? ( - - {usdBalance} - - ) : null} + ) + } + captionRight={ + !rightElement && ( + <> + {balance.amount.toNumber() > 0 && address ? usdBalance : null} {additionalUsdBalanceInfo} - - } - rightElement={rightElement} - /> - - {component} - + + ) + } + /> + ); } diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.tsx b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.tsx deleted file mode 100644 index bddbae243ac..00000000000 --- a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; - -import { spamFilter } from '@app/common/utils/spam-filter'; - -import { CryptoCurrencyAssetItemLayout } from './crypto-currency-asset-item.layout'; - -interface CryptoCurrencyAssetItemProps { - assetBalance: AllCryptoCurrencyAssetBalances; - icon: React.ReactNode; - usdBalance?: string; - address?: string; - isPressable?: boolean; - additionalBalanceInfo?: React.ReactNode; - additionalUsdBalanceInfo?: React.ReactNode; - rightElement?: React.ReactNode; - onClick?(): void; -} -export function CryptoCurrencyAssetItem({ - additionalBalanceInfo, - additionalUsdBalanceInfo, - address, - assetBalance, - icon, - isPressable, - onClick, - rightElement, - usdBalance, -}: CryptoCurrencyAssetItemProps) { - const { balance, asset } = assetBalance; - - return ( - - ); -} diff --git a/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts new file mode 100644 index 00000000000..041ca288ea5 --- /dev/null +++ b/src/app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset.utils.ts @@ -0,0 +1,28 @@ +import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; + +import { AllCryptoCurrencyAssetBalances } from '@shared/models/crypto-asset-balance.model'; + +import { formatBalance } from '@app/common/format-balance'; +import { ftDecimals } from '@app/common/stacks-utils'; +import { spamFilter } from '@app/common/utils/spam-filter'; + +export function parseCryptoCurrencyAssetBalance(assetBalance: AllCryptoCurrencyAssetBalances) { + const { asset, balance } = assetBalance; + + const amount = balance.decimals + ? ftDecimals(balance.amount, balance.decimals) + : balance.amount.toString(); + const dataTestId = CryptoAssetSelectors.CryptoAssetListItem.replace( + '{symbol}', + balance.symbol.toLowerCase() + ); + const formattedBalance = formatBalance(amount); + const title = spamFilter(asset.name); + + return { + balance, + dataTestId, + formattedBalance, + title, + }; +} diff --git a/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx b/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx index 13fd5147231..08d771dd2d1 100644 --- a/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx +++ b/src/app/components/crypto-assets/stacks/components/stacks-asset-avatar.tsx @@ -2,14 +2,12 @@ import { Box, BoxProps } from 'leather-styles/jsx'; import { DynamicColorCircle } from '@app/ui/components/dynamic-color-circle'; -import { StacksUnanchoredStatusIcon } from './stacks-unanchored-status-icon'; import { StxAvatar } from './stx-avatar'; interface StacksAssetAvatarProps extends BoxProps { gradientString?: string; imageCanonicalUri?: string; isStx?: boolean; - isUnanchored?: boolean; size?: string; } export function StacksAssetAvatar({ @@ -17,8 +15,7 @@ export function StacksAssetAvatar({ gradientString, imageCanonicalUri, isStx, - isUnanchored, - size = '36', + size = '40', ...props }: StacksAssetAvatarProps) { if (isStx) return ; @@ -33,7 +30,6 @@ export function StacksAssetAvatar({ return ( {children} - {isUnanchored ? : null} ); } diff --git a/src/app/components/crypto-assets/stacks/components/stacks-unanchored-status-icon.tsx b/src/app/components/crypto-assets/stacks/components/stacks-unanchored-status-icon.tsx deleted file mode 100644 index b412c6df1a8..00000000000 --- a/src/app/components/crypto-assets/stacks/components/stacks-unanchored-status-icon.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { TransactionTypeIconWrapper } from '@app/components/transaction/transaction-type-icon-wrapper'; -import { ZapIcon } from '@app/ui/components/icons/zap-icon'; - -export function StacksUnanchoredStatusIcon() { - return } />; -} diff --git a/src/app/components/crypto-assets/stacks/components/stx-avatar.tsx b/src/app/components/crypto-assets/stacks/components/stx-avatar.tsx index 34bba2f3c0a..f1205936f29 100644 --- a/src/app/components/crypto-assets/stacks/components/stx-avatar.tsx +++ b/src/app/components/crypto-assets/stacks/components/stx-avatar.tsx @@ -2,12 +2,7 @@ import { Circle, CircleProps } from 'leather-styles/jsx'; import { StxIcon } from '@app/ui/components/icons/stx-icon'; -import { StacksUnanchoredStatusIcon } from './stacks-unanchored-status-icon'; - -interface StxAvatarProps extends CircleProps { - isUnanchored?: boolean; -} -export function StxAvatar({ isUnanchored, ...props }: StxAvatarProps) { +export function StxAvatar({ ...props }: CircleProps) { return ( - {isUnanchored ? : null} ); } diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.tsx b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts similarity index 56% rename from src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.tsx rename to src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts index 3e62de0873e..06336273364 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.tsx +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/fungible-token-asset.utils.ts @@ -1,48 +1,41 @@ import { CryptoAssetSelectors } from '@tests/selectors/crypto-asset.selectors'; -import { FlexProps } from 'leather-styles/jsx'; import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import { Money } from '@shared/models/money.model'; import { getImageCanonicalUri } from '@app/common/crypto-assets/stacks-crypto-asset.utils'; +import { formatBalance } from '@app/common/format-balance'; +import { ftDecimals } from '@app/common/stacks-utils'; import { formatContractId, getTicker } from '@app/common/utils'; import { spamFilter } from '@app/common/utils/spam-filter'; import { getAssetName } from '@app/ui/utils/get-asset-name'; -import { StacksFungibleTokenAssetItemLayout } from './stacks-fungible-token-asset-item.layout'; - -interface StacksFungibleTokenAssetItemProps extends FlexProps { - assetBalance: StacksFungibleTokenAssetBalance; - unanchoredAssetBalance?: Money; - isPressable?: boolean; - onClick?(): void; -} -export function StacksFungibleTokenAssetItem({ - assetBalance, - isPressable, - onClick, -}: StacksFungibleTokenAssetItemProps) { +export function parseStacksFungibleTokenAssetBalance( + assetBalance: StacksFungibleTokenAssetBalance +) { const { asset, balance } = assetBalance; const { contractAddress, contractAssetName, contractName, name, symbol } = asset; + const amount = balance.decimals + ? ftDecimals(balance.amount, balance.decimals || 0) + : balance.amount.toString(); const avatar = `${formatContractId(contractAddress, contractName)}::${contractAssetName}`; const dataTestId = symbol && CryptoAssetSelectors.CryptoAssetListItem.replace('{symbol}', symbol.toLowerCase()); + const formattedBalance = formatBalance(amount); const friendlyName = name || (contractAssetName.includes('::') ? getAssetName(contractAssetName) : contractAssetName); const imageCanonicalUri = getImageCanonicalUri(asset.imageCanonicalUri, asset.name); const caption = symbol || getTicker(friendlyName); - return ( - - ); + const title = spamFilter(friendlyName); + + return { + amount, + avatar, + caption, + dataTestId, + formattedBalance, + imageCanonicalUri, + title, + }; } diff --git a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx index a259f6fbf0b..79b0f6fc747 100644 --- a/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx +++ b/src/app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout.tsx @@ -1,47 +1,29 @@ -import { Flex, FlexProps, styled } from 'leather-styles/jsx'; +import { styled } from 'leather-styles/jsx'; -import type { Money } from '@shared/models/money.model'; +import { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import { formatBalance } from '@app/common/format-balance'; -import { ftDecimals } from '@app/common/stacks-utils'; import { StacksAssetAvatar } from '@app/components/crypto-assets/stacks/components/stacks-asset-avatar'; -import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { AssetCaption } from '../../components/asset-caption'; -import { AssetRowGrid } from '../../components/asset-row-grid'; +import { parseStacksFungibleTokenAssetBalance } from './fungible-token-asset.utils'; -interface StacksFungibleTokenAssetItemLayoutProps extends FlexProps { - avatar: string; - balance: Money; - caption: string; - imageCanonicalUri?: string; - isPressable?: boolean; - title: string; +interface StacksFungibleTokenAssetItemLayoutProps { + assetBalance: StacksFungibleTokenAssetBalance; onClick?(): void; } export function StacksFungibleTokenAssetItemLayout({ - avatar, - balance, - caption, - imageCanonicalUri, - isPressable, - title, + assetBalance, onClick, }: StacksFungibleTokenAssetItemLayoutProps) { - const [component, bind] = usePressable(isPressable); - - const amount = balance.decimals - ? ftDecimals(balance.amount, balance.decimals || 0) - : balance.amount.toString(); - const formattedBalance = formatBalance(amount); + const { amount, avatar, caption, dataTestId, formattedBalance, imageCanonicalUri, title } = + parseStacksFungibleTokenAssetBalance(assetBalance); return ( - - + } - spacing="space.04" - width="100%" - > - {title}} - balance={ - - - {formattedBalance.value} - - - } - caption={} - /> - {component} - - + titleLeft={title} + captionLeft={caption} + titleRight={ + + + {formattedBalance.value} + + + } + /> + ); } diff --git a/src/app/components/inscription-preview-card/inscription-preview-card.tsx b/src/app/components/inscription-preview-card/inscription-preview-card.tsx index d0895f041db..e3581bba398 100644 --- a/src/app/components/inscription-preview-card/inscription-preview-card.tsx +++ b/src/app/components/inscription-preview-card/inscription-preview-card.tsx @@ -1,4 +1,5 @@ -import { Flag } from '../layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; + import { InscriptionMetadata } from './components/inscription-metadata'; interface InscriptionPreviewCardProps { @@ -21,7 +22,6 @@ export function InscriptionPreviewCard({ }: InscriptionPreviewCardProps) { return ( } + align="top" alignItems="center" justifyContent="center" py="space.04" diff --git a/src/app/components/secret-key/mnemonic-key/mnemonic-word-input.tsx b/src/app/components/secret-key/mnemonic-key/mnemonic-word-input.tsx index 257d8a38bc4..7bb301386cc 100644 --- a/src/app/components/secret-key/mnemonic-key/mnemonic-word-input.tsx +++ b/src/app/components/secret-key/mnemonic-key/mnemonic-word-input.tsx @@ -23,7 +23,7 @@ export function MnemonicWordInput({ const [isFocused, setIsFocused] = useState(false); const isDirty = useIsFieldDirty(name); return ( - + diff --git a/src/app/components/stacks-transaction-item/stacks-transaction-item.tsx b/src/app/components/stacks-transaction-item/stacks-transaction-item.tsx index cc143499190..da082aaaf8b 100644 --- a/src/app/components/stacks-transaction-item/stacks-transaction-item.tsx +++ b/src/app/components/stacks-transaction-item/stacks-transaction-item.tsx @@ -1,7 +1,5 @@ import { createSearchParams, useLocation, useNavigate } from 'react-router-dom'; -import { BoxProps, styled } from 'leather-styles/jsx'; - import { StacksTx, TxTransferDetails } from '@shared/models/transactions/stacks-transaction.model'; import { RouteUrls } from '@shared/route-urls'; @@ -16,7 +14,6 @@ import { import { useWalletType } from '@app/common/use-wallet-type'; import { whenPageMode } from '@app/common/utils'; import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; -import { usePressable } from '@app/components/item-hover'; import { TransactionTitle } from '@app/components/transaction/transaction-title'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useRawTxIdState } from '@app/store/transactions/raw.hooks'; @@ -26,16 +23,14 @@ import { IncreaseFeeButton } from './increase-fee-button'; import { StacksTransactionIcon } from './stacks-transaction-icon'; import { StacksTransactionStatus } from './stacks-transaction-status'; -interface StacksTransactionItemProps extends BoxProps { +interface StacksTransactionItemProps { transferDetails?: TxTransferDetails; transaction?: StacksTx; } export function StacksTransactionItem({ transferDetails, transaction, - ...rest }: StacksTransactionItemProps) { - const [component, bind, { isHovered }] = usePressable(true); const { handleOpenStacksTxLink: handleOpenTxLink } = useStacksExplorerLink(); const currentAccount = useCurrentStacksAccount(); const analytics = useAnalytics(); @@ -83,32 +78,21 @@ export function StacksTransactionItem({ const increaseFeeButton = ( ); const txStatus = transaction && ; - const txCaption = ( - - {caption} - - ); - const txValue = {value}; return ( } - txValue={txValue} - belowCaptionEl={increaseFeeButton} - {...bind} - {...rest} - > - {component} - + txValue={value} + /> ); } diff --git a/src/app/components/stacks-transaction-item/stacks-transaction-status.tsx b/src/app/components/stacks-transaction-item/stacks-transaction-status.tsx index f3075f06311..8661e0c562d 100644 --- a/src/app/components/stacks-transaction-item/stacks-transaction-status.tsx +++ b/src/app/components/stacks-transaction-item/stacks-transaction-status.tsx @@ -1,12 +1,11 @@ -import { styled } from 'leather-styles/jsx'; - import { StacksTx } from '@shared/models/transactions/stacks-transaction.model'; import { isPendingTx } from '@app/common/transactions/stacks/transaction.utils'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; +import { Caption } from '@app/ui/components/typography/caption'; -import { MicroblockLabel } from '../transaction/microblock-label'; -import { PendingLabel } from '../transaction/pending-label'; +const pendingWaitingMessage = + 'This transaction is waiting to be confirmed. Depending on network congestion, this may take anywhere from a few minutes, to a couple of hours.'; interface TransactionStatusProps { transaction: StacksTx; @@ -14,18 +13,17 @@ interface TransactionStatusProps { export function StacksTransactionStatus({ transaction }: TransactionStatusProps) { const isPending = isPendingTx(transaction); const isFailed = !isPending && transaction.tx_status !== 'success'; - const isInMicroblock = - !isPending && transaction.tx_status === 'success' && transaction.is_unanchored; return ( <> - {isPending && } - {isInMicroblock && } + {isPending && ( + + Pending + + )} {isFailed && ( - - Failed - + Failed )} diff --git a/src/app/components/transaction-item/transaction-item.layout.tsx b/src/app/components/transaction-item/transaction-item.layout.tsx index bb961b93108..921b9f66998 100644 --- a/src/app/components/transaction-item/transaction-item.layout.tsx +++ b/src/app/components/transaction-item/transaction-item.layout.tsx @@ -1,49 +1,51 @@ import { ReactNode } from 'react'; -import { Box, Flex, HStack } from 'leather-styles/jsx'; +import { HStack, styled } from 'leather-styles/jsx'; + +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; +import { Caption } from '@app/ui/components/typography/caption'; interface TransactionItemLayoutProps { openTxLink(): void; + rightElement?: ReactNode; txCaption: ReactNode; txTitle: ReactNode; txValue: ReactNode; txIcon?: ReactNode; txStatus?: ReactNode; - belowCaptionEl?: ReactNode; children?: ReactNode; } export function TransactionItemLayout({ openTxLink, + rightElement, txCaption, txIcon, txStatus, txTitle, txValue, - belowCaptionEl, - children, - ...rest }: TransactionItemLayoutProps) { return ( - - - {txIcon && txIcon} - - - {txTitle} {txValue} - + + - {txCaption} {txStatus && txStatus} - {belowCaptionEl ? belowCaptionEl : null} + {txCaption} + {txStatus && txStatus} - - - {children} - + } + titleRight={ + rightElement ? ( + rightElement + ) : ( + + {txValue} + + ) + } + /> + ); } diff --git a/src/app/components/transaction/microblock-label.tsx b/src/app/components/transaction/microblock-label.tsx deleted file mode 100644 index 6db87cf84e2..00000000000 --- a/src/app/components/transaction/microblock-label.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Box, Flex, styled } from 'leather-styles/jsx'; - -import { InfoIcon } from '@app/ui/components/icons/info-icon'; -import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; - -const inMicroblockMessage = - 'This transaction is currently in a microblock, which increases the chances of inclusion in the next anchor block.'; - -export function MicroblockLabel() { - return ( - - - In microblock - - - - - - - - ); -} diff --git a/src/app/components/transaction/pending-label.tsx b/src/app/components/transaction/pending-label.tsx deleted file mode 100644 index 939a42bc5e5..00000000000 --- a/src/app/components/transaction/pending-label.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Box, Flex, styled } from 'leather-styles/jsx'; - -import { InfoIcon } from '@app/ui/components/icons/info-icon'; -import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; - -const defaultPendingWaitingMessage = - 'This transaction is waiting to be confirmed. Depending on network congestion, this may take anywhere from a few minutes, to a couple of hours.'; - -interface PendingLabelProps { - pendingWaitingMessage?: string; -} - -export function PendingLabel({ - pendingWaitingMessage = defaultPendingWaitingMessage, -}: PendingLabelProps) { - return ( - - - Pending - - - - - - - - ); -} diff --git a/src/app/components/transaction/token-transfer-icon.tsx b/src/app/components/transaction/token-transfer-icon.tsx index 7e12e5f7f5c..d6cb371fde1 100644 --- a/src/app/components/transaction/token-transfer-icon.tsx +++ b/src/app/components/transaction/token-transfer-icon.tsx @@ -3,25 +3,12 @@ import { StacksTx } from '@shared/models/transactions/stacks-transaction.model'; import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { ArrowDownIcon } from '@app/ui/components/icons/arrow-down-icon'; import { ArrowUpIcon } from '@app/ui/components/icons/arrow-up-icon'; -import { ZapIcon } from '@app/ui/components/icons/zap-icon'; - -import { getColorFromTx } from './transaction-type-icon'; export function TokenTransferIcon(props: { tx: StacksTx }) { const { tx } = props; const currentAccountStxAddress = useCurrentAccountStxAddressState(); const isSent = tx.sender_address === currentAccountStxAddress; - if ('is_unanchored' in tx && tx.is_unanchored) - return ( - - ); - if (isSent) return ; return ; diff --git a/src/app/components/transaction/transaction-icon.tsx b/src/app/components/transaction/transaction-icon.tsx index 38297ead18e..f701007643d 100644 --- a/src/app/components/transaction/transaction-icon.tsx +++ b/src/app/components/transaction/transaction-icon.tsx @@ -18,8 +18,6 @@ export function TransactionIcon(props: { tx: StacksTx }) { return ; case 'contract_call': return ; - case 'poison_microblock': - return null; default: return null; } diff --git a/src/app/components/transaction/transaction-title.tsx b/src/app/components/transaction/transaction-title.tsx index 6f9bf4af05c..23397108de8 100644 --- a/src/app/components/transaction/transaction-title.tsx +++ b/src/app/components/transaction/transaction-title.tsx @@ -22,10 +22,11 @@ export function TransactionTitle(props: TransactionTitleProps) { return ( {spamFilter(title)} diff --git a/src/app/components/transaction/transaction-type-icon.tsx b/src/app/components/transaction/transaction-type-icon.tsx index a4634d66d2a..d0a236ff1ea 100644 --- a/src/app/components/transaction/transaction-type-icon.tsx +++ b/src/app/components/transaction/transaction-type-icon.tsx @@ -7,11 +7,10 @@ import { TransactionTypeIconWrapper } from './transaction-type-icon-wrapper'; type StatusColorMap = Record<StacksTxStatus, string>; -export function getColorFromTx(tx: StacksTx) { +function getColorFromTx(tx: StacksTx) { const colorMap: StatusColorMap = { pending: 'warning.label', - success_microblock: 'stacks', - success_anchor_block: 'stacks', + success: 'stacks', failed: 'error.label', }; diff --git a/src/app/components/warning-label.tsx b/src/app/components/warning-label.tsx index 86cd2f890ce..bdaa47c452a 100644 --- a/src/app/components/warning-label.tsx +++ b/src/app/components/warning-label.tsx @@ -1,10 +1,9 @@ import { Box, BoxProps, styled } from 'leather-styles/jsx'; import { token } from 'leather-styles/tokens'; +import { Flag } from '@app/ui/components/flag/flag'; import { ErrorCircleIcon } from '@app/ui/components/icons/error-circle-icon'; -import { Flag } from './layout/flag'; - interface WarningLabelProps extends BoxProps { title?: string; } @@ -12,6 +11,7 @@ export function WarningLabel({ children, title, ...props }: WarningLabelProps) { return ( <Box bg="warning.background" borderRadius="sm" {...props}> <Flag + align="top" color="accent.notification-text" img={<ErrorCircleIcon style={{ color: token('colors.warning.label') }} />} minHeight="48px" diff --git a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx index 3d2fb787563..44acfa0e297 100644 --- a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx +++ b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.layout.tsx @@ -11,7 +11,7 @@ export function PendingTransactionListLayout({ children }: PendingTransactionLis <styled.span color="accent.text-subdued" textStyle="body.02"> Pending </styled.span> - <Stack mt="space.04" pb="space.06" gap="space.05"> + <Stack mt="space.04" pb="space.06"> {children} </Stack> </> diff --git a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx index 6fa22d355a1..2fe58970c23 100644 --- a/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx +++ b/src/app/features/activity-list/components/pending-transaction-list/pending-transaction-list.tsx @@ -2,9 +2,9 @@ import { MempoolTransaction } from '@stacks/stacks-blockchain-api-types'; import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; +import { BitcoinTransactionItem } from '@app/components/bitcoin-transaction-item/bitcoin-transaction-item'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; -import { BitcoinTransactionItem } from '../../../../components/bitcoin-transaction-item/bitcoin-transaction-item'; import { PendingTransactionListLayout } from './pending-transaction-list.layout'; interface PendingTransactionListProps { diff --git a/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx b/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx index eed16bebc44..7d3f7c2e50a 100644 --- a/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx +++ b/src/app/features/activity-list/components/transaction-list/transaction-list.layout.tsx @@ -6,9 +6,5 @@ interface TransactionListLayoutProps { children: ReactNode; } export function TransactionListLayout({ children }: TransactionListLayoutProps) { - return ( - <Stack pb="space.06" gap="space.06"> - {children} - </Stack> - ); + return <Stack pb="space.06">{children}</Stack>; } diff --git a/src/app/features/activity-list/components/transaction-list/transaction-list.utils.ts b/src/app/features/activity-list/components/transaction-list/transaction-list.utils.ts index 8d8dc60362b..976ce32fed9 100644 --- a/src/app/features/activity-list/components/transaction-list/transaction-list.utils.ts +++ b/src/app/features/activity-list/components/transaction-list/transaction-list.utils.ts @@ -44,15 +44,6 @@ function getTransactionTxIndex(listTx: TransactionListTxs) { } } -function getTransactionMicroblockSequence(listTx: TransactionListTxs) { - switch (listTx.blockchain) { - case 'stacks': - return listTx.transaction.tx.microblock_sequence; - default: - return undefined; - } -} - function getTransactionBlockHeight(listTx: TransactionListTxs) { switch (listTx.blockchain) { case 'bitcoin': @@ -74,17 +65,6 @@ function groupTxsByDateMap(txs: TransactionListTxs[]) { if (!aTxIndex || !bTxIndex) return 0; return aTxIndex > bTxIndex ? -1 : aTxIndex < bTxIndex ? 1 : 0; }) - .sort((a, b) => { - const aMicroblockSequence = getTransactionMicroblockSequence(a); - const bMicroblockSequence = getTransactionMicroblockSequence(b); - - if (!aMicroblockSequence || !bMicroblockSequence) return 0; - return aMicroblockSequence > bMicroblockSequence - ? -1 - : aMicroblockSequence < bMicroblockSequence - ? 1 - : 0; - }) .sort((a, b) => { const aBlockHeight = getTransactionBlockHeight(a); const bBlockHeight = getTransactionBlockHeight(b); diff --git a/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx b/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx index 117d795d447..f9b3bde1d8d 100644 --- a/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx +++ b/src/app/features/activity-list/components/transaction-list/transactions-by-date.layout.tsx @@ -17,9 +17,7 @@ export function TransactionsByDateLayout({ <styled.span textStyle="body.02" color="accent.text-subdued"> {displayDate} </styled.span> - <Stack mt="space.04" gap="space.05"> - {children} - </Stack> + <Stack mt="space.04">{children}</Stack> </Box> ); } diff --git a/src/app/features/asset-list/asset-list.tsx b/src/app/features/asset-list/asset-list.tsx index d6703e2c534..096f86f20b0 100644 --- a/src/app/features/asset-list/asset-list.tsx +++ b/src/app/features/asset-list/asset-list.tsx @@ -7,7 +7,7 @@ import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balanc import { useWalletType } from '@app/common/use-wallet-type'; import { BitcoinContractEntryPoint } from '@app/components/bitcoin-contract-entry-point/bitcoin-contract-entry-point'; import { Brc20TokensLoader } from '@app/components/brc20-tokens-loader'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; import { CurrentStacksAccountLoader } from '@app/components/loaders/stacks-account-loader'; import { useHasBitcoinLedgerKeychain } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useCurrentAccountNativeSegwitAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; @@ -31,10 +31,10 @@ export function AssetsList() { const { whenWallet } = useWalletType(); return ( - <Stack pb="space.06" gap="space.05" data-testid={HomePageSelectors.BalancesList}> + <Stack data-testid={HomePageSelectors.BalancesList} pb="space.06"> {whenWallet({ software: ( - <CryptoCurrencyAssetItem + <CryptoCurrencyAssetItemLayout assetBalance={btcAvailableAssetBalance} usdBalance={btcAvailableUsdBalance} icon={<BtcIcon />} @@ -42,7 +42,7 @@ export function AssetsList() { /> ), ledger: ( - <CryptoCurrencyAssetItem + <CryptoCurrencyAssetItemLayout assetBalance={btcAvailableAssetBalance} usdBalance={btcAvailableUsdBalance} icon={<BtcIcon />} diff --git a/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx b/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx index a09c669a03f..ff0f0ed281a 100644 --- a/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx +++ b/src/app/features/asset-list/components/add-stacks-ledger-keys-item.tsx @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-avatar'; import { createStacksCryptoCurrencyAssetTypeWrapper } from '@app/query/stacks/balance/stacks-ft-balances.utils'; @@ -8,7 +8,7 @@ import { ConnectLedgerAssetBtn } from './connect-ledger-asset-button'; export function AddStacksLedgerKeysItem() { return ( - <CryptoCurrencyAssetItem + <CryptoCurrencyAssetItemLayout assetBalance={createStacksCryptoCurrencyAssetTypeWrapper(new BigNumber(0))} icon={<StxAvatar />} rightElement={<ConnectLedgerAssetBtn chain="stacks" />} diff --git a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx b/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx index c8ce70ef4b9..e71915b3ee3 100644 --- a/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx +++ b/src/app/features/asset-list/components/bitcoin-fungible-tokens-asset-list.tsx @@ -1,6 +1,6 @@ import { Stack } from 'leather-styles/jsx'; -import { Brc20TokenAssetItem } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item'; +import { Brc20TokenAssetItemLayout } from '@app/components/crypto-assets/bitcoin/brc20-token-asset-list/components/brc20-token-asset-item.layout'; import { Brc20Token } from '@app/query/bitcoin/ordinals/brc20/brc20-tokens.query'; interface BitcoinFungibleTokenAssetListProps { @@ -12,7 +12,7 @@ export function BitcoinFungibleTokenAssetList({ brc20Tokens }: BitcoinFungibleTo return ( <Stack gap="space.05"> {brc20Tokens.map(token => ( - <Brc20TokenAssetItem key={token.tick} token={token} /> + <Brc20TokenAssetItemLayout key={token.tick} token={token} /> ))} </Stack> ); diff --git a/src/app/features/asset-list/components/stacks-asset-list.tsx b/src/app/features/asset-list/components/stacks-asset-list.tsx index 40207ba6177..a58c3a82834 100644 --- a/src/app/features/asset-list/components/stacks-asset-list.tsx +++ b/src/app/features/asset-list/components/stacks-asset-list.tsx @@ -2,9 +2,9 @@ import { styled } from 'leather-styles/jsx'; import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { ftDecimals } from '@app/common/stacks-utils'; -import { CryptoCurrencyAssetItem } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item'; +import { CryptoCurrencyAssetItemLayout } from '@app/components/crypto-assets/crypto-currency-asset/crypto-currency-asset-item.layout'; import { StxAvatar } from '@app/components/crypto-assets/stacks/components/stx-avatar'; -import { useStacksFungibleTokenAssetBalancesAnchoredWithMetadata } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; +import { useStacksFungibleTokenAssetBalancesWithMetadata } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; import { BulletOperator } from '@app/ui/components/bullet-separator/bullet-separator'; import { Caption } from '@app/ui/components/typography/caption'; @@ -15,9 +15,7 @@ interface StacksAssetListProps { account: StacksAccount; } export function StacksAssetList({ account }: StacksAssetListProps) { - const stacksFtAssetBalances = useStacksFungibleTokenAssetBalancesAnchoredWithMetadata( - account.address - ); + const stacksFtAssetBalances = useStacksFungibleTokenAssetBalancesWithMetadata(account.address); const { stxEffectiveBalance, stxEffectiveUsdBalance, stxLockedBalance, stxUsdLockedBalance } = useStxBalance(); @@ -38,7 +36,7 @@ export function StacksAssetList({ account }: StacksAssetListProps) { return ( <> - <CryptoCurrencyAssetItem + <CryptoCurrencyAssetItemLayout assetBalance={stxEffectiveBalance} usdBalance={stxEffectiveUsdBalance} address={account.address} diff --git a/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx b/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx index c6768ef2666..5061426a76f 100644 --- a/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx +++ b/src/app/features/asset-list/components/stacks-fungible-token-asset-list.tsx @@ -2,7 +2,7 @@ import { Stack } from 'leather-styles/jsx'; import type { StacksFungibleTokenAssetBalance } from '@shared/models/crypto-asset-balance.model'; -import { StacksFungibleTokenAssetItem } from '@app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item'; +import { StacksFungibleTokenAssetItemLayout } from '@app/components/crypto-assets/stacks/fungible-token-asset/stacks-fungible-token-asset-item.layout'; interface StacksFungibleTokenAssetListProps { assetBalances: StacksFungibleTokenAssetBalance[]; @@ -10,9 +10,9 @@ interface StacksFungibleTokenAssetListProps { export function StacksFungibleTokenAssetList({ assetBalances }: StacksFungibleTokenAssetListProps) { if (assetBalances.length === 0) return null; return ( - <Stack gap="space.05"> + <Stack> {assetBalances.map(assetBalance => ( - <StacksFungibleTokenAssetItem + <StacksFungibleTokenAssetItemLayout assetBalance={assetBalance} key={assetBalance.asset.contractId} /> diff --git a/src/app/features/collectibles/components/collectibes.layout.tsx b/src/app/features/collectibles/components/collectibes.layout.tsx index ccdae07f19e..091fa5196c5 100644 --- a/src/app/features/collectibles/components/collectibes.layout.tsx +++ b/src/app/features/collectibles/components/collectibes.layout.tsx @@ -23,7 +23,7 @@ export function CollectiblesLayout({ }: CollectiblesLayoutProps) { return ( <> - <Flex flexDirection="row" justifyContent="space-between" flex={1}> + <Flex flexDirection="row" justifyContent="space-between" flex={1} mt="space.05"> <HStack columnGap="space.02"> <styled.span textStyle="label.01">{title}</styled.span> {isLoading ? ( diff --git a/src/app/features/current-account/popup-header.tsx b/src/app/features/current-account/popup-header.tsx index 10c25516195..f77428dc1b9 100644 --- a/src/app/features/current-account/popup-header.tsx +++ b/src/app/features/current-account/popup-header.tsx @@ -5,13 +5,13 @@ import { token } from 'leather-styles/tokens'; import { BtcBalance } from '@app/components/balance-btc'; import { StxBalance } from '@app/components/balance-stx'; -import { Flag } from '@app/components/layout/flag'; import { LoadingRectangle } from '@app/components/loading-rectangle'; import { NetworkModeBadge } from '@app/components/network-mode-badge'; import { CurrentAccountAvatar } from '@app/features/current-account/current-account-avatar'; import { CurrentAccountName } from '@app/features/current-account/current-account-name'; import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { Flag } from '@app/ui/components/flag/flag'; interface PopupHeaderLayoutProps { children: React.ReactNode; @@ -33,7 +33,6 @@ function PopupHeaderSuspense({ displayAddresssBalanceOf = 'stx' }: PopupHeaderPr return ( <PopupHeaderLayout> <Flag - align="middle" img={ <CurrentAccountAvatar color={token('colors.white')} diff --git a/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx b/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx index c9520a69f7b..bb6bff62f2a 100644 --- a/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx +++ b/src/app/features/increase-fee-drawer/components/increase-btc-fee-form.tsx @@ -50,7 +50,7 @@ export function IncreaseBtcFeeForm({ btcTx }: IncreaseBtcFeeFormProps) { validationSchema={validationSchema} > <Stack gap="space.06"> - {btcTx && <BitcoinTransactionItem position="relative" transaction={btcTx} zIndex={99} />} + {btcTx && <BitcoinTransactionItem transaction={btcTx} />} <Stack gap="space.04"> <Stack gap="space.01"> <TextInputField label={feeInputLabel} name="feeRate" placeholder={feeInputLabel} /> diff --git a/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx b/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx index e7620a13e18..3d010093475 100644 --- a/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx +++ b/src/app/features/increase-fee-drawer/components/increase-stx-fee-form.tsx @@ -13,15 +13,13 @@ import { useRefreshAllAccountData } from '@app/common/hooks/account/use-refresh- import { useStxBalance } from '@app/common/hooks/balance/stx/use-stx-balance'; import { microStxToStx, stxToMicroStx } from '@app/common/money/unit-conversion'; import { stacksValue } from '@app/common/stacks-utils'; -import { useWalletType } from '@app/common/use-wallet-type'; import { safelyFormatHexTxid } from '@app/common/utils/safe-handle-txid'; import { stxFeeValidator } from '@app/common/validation/forms/fee-validators'; import { LoadingSpinner } from '@app/components/loading-spinner'; import { StacksTransactionItem } from '@app/components/stacks-transaction-item/stacks-transaction-item'; -import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; -import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useSubmittedTransactionsActions } from '@app/store/submitted-transactions/submitted-transactions.hooks'; -import { useReplaceByFeeSoftwareWalletSubmitCallBack } from '@app/store/transactions/fees.hooks'; import { useRawDeserializedTxState, useRawTxIdState } from '@app/store/transactions/raw.hooks'; import { Caption } from '@app/ui/components/typography/caption'; @@ -34,13 +32,11 @@ export function IncreaseStxFeeForm() { const tx = useSelectedTx(); const navigate = useNavigate(); const [, setTxId] = useRawTxIdState(); - const replaceByFee = useReplaceByFeeSoftwareWalletSubmitCallBack(); - const { data: balances } = useCurrentStacksAccountAnchoredBalances(); + const { data: balances } = useCurrentStacksAccountBalances(); const { availableBalance } = useStxBalance(); const submittedTransactionsActions = useSubmittedTransactionsActions(); const rawTx = useRawDeserializedTxState(); - const { whenWallet } = useWalletType(); - const ledgerNavigate = useLedgerNavigate(); + const { stacksBroadcastTransaction } = useStacksBroadcastTransaction('STX'); const fee = Number(rawTx?.auth.spendingCondition?.fee); @@ -58,24 +54,9 @@ export function IncreaseStxFeeForm() { const txId = tx.tx_id || safelyFormatHexTxid(rawTx.txid()); await refreshAccountData(); submittedTransactionsActions.transactionReplacedByFee(txId); - await whenWallet({ - software: async () => { - await replaceByFee(rawTx); - }, - ledger: () => { - ledgerNavigate.toConnectAndSignStacksTransactionStep(rawTx); - }, - })(); + await stacksBroadcastTransaction(rawTx); }, - [ - tx, - rawTx, - refreshAccountData, - submittedTransactionsActions, - whenWallet, - replaceByFee, - ledgerNavigate, - ] + [tx, rawTx, refreshAccountData, submittedTransactionsActions, stacksBroadcastTransaction] ); if (!tx || !fee) return <LoadingSpinner />; @@ -93,7 +74,7 @@ export function IncreaseStxFeeForm() { > {props => ( <Stack gap="space.06"> - {tx && <StacksTransactionItem position="relative" transaction={tx} zIndex={99} />} + {tx && <StacksTransactionItem transaction={tx} />} <Stack gap="space.04"> <IncreaseFeeField currentFee={fee} /> {balances?.stx.unlockedStx.amount && ( diff --git a/src/app/features/increase-fee-drawer/hooks/use-btc-increase-fee.ts b/src/app/features/increase-fee-drawer/hooks/use-btc-increase-fee.ts index bbd86d8eef3..212ec4d01f1 100644 --- a/src/app/features/increase-fee-drawer/hooks/use-btc-increase-fee.ts +++ b/src/app/features/increase-fee-drawer/hooks/use-btc-increase-fee.ts @@ -8,6 +8,7 @@ import * as yup from 'yup'; import { createMoney } from '@shared/models/money.model'; import { BitcoinTx } from '@shared/models/transactions/bitcoin-transaction.model'; import { RouteUrls } from '@shared/route-urls'; +import { isError } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { useBtcAssetBalance } from '@app/common/hooks/balance/btc/use-btc-balance'; @@ -122,7 +123,7 @@ export function useBtcIncreaseFee(btcTx: BitcoinTx) { } function onError(error: unknown) { - const message = error instanceof Error ? error.message : 'Unknown error'; + const message = isError(error) ? error.message : 'Unknown error'; toast.error(message); navigate(RouteUrls.Home); } diff --git a/src/app/features/leather-intro-dialog/leather-intro-steps.tsx b/src/app/features/leather-intro-dialog/leather-intro-steps.tsx index 29b62738bfc..48d8f80131a 100644 --- a/src/app/features/leather-intro-dialog/leather-intro-steps.tsx +++ b/src/app/features/leather-intro-dialog/leather-intro-steps.tsx @@ -16,7 +16,7 @@ export function LeatherIntroDialog({ children }: HasChildren) { return ( <Dialog.Root defaultOpen> <Dialog.Content - // Prevent immediate closing, force interation + // Prevent immediate closing, force interaction onEscapeKeyDown={e => e.preventDefault()} onInteractOutside={e => e.preventDefault()} className={css({ maxWidth: '500px', backgroundColor: 'accent.background-primary' })} diff --git a/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx b/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx index d6203b39eff..2ce87ffb6c1 100644 --- a/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx +++ b/src/app/features/ledger/flows/bitcoin-tx-signing/ledger-bitcoin-sign-tx-container.tsx @@ -1,35 +1,35 @@ import { useEffect, useState } from 'react'; import toast from 'react-hot-toast'; -import { Outlet, Route, useLocation } from 'react-router-dom'; +import { Route, useLocation } from 'react-router-dom'; import * as btc from '@scure/btc-signer'; import { hexToBytes } from '@stacks/common'; +import BitcoinApp from 'ledger-bitcoin'; import get from 'lodash.get'; import { BitcoinInputSigningConfig } from '@shared/crypto/bitcoin/signer-config'; import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; -import { useLocationState, useLocationStateWithCache } from '@app/common/hooks/use-location-state'; +import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { appEvents } from '@app/common/publish-subscribe'; import { delay } from '@app/common/utils'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; +import { ApproveSignLedgerBitcoinTx } from '@app/features/ledger/flows/bitcoin-tx-signing/steps/approve-bitcoin-sign-ledger-tx'; +import { ledgerSignTxRoutes } from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx-route-generator'; +import { LedgerTxSigningContext } from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx.context'; +import { TxSigningFlow } from '@app/features/ledger/generic-flows/tx-signing/tx-signing-flow'; +import { useLedgerSignTx } from '@app/features/ledger/generic-flows/tx-signing/use-ledger-sign-tx'; +import { useLedgerAnalytics } from '@app/features/ledger/hooks/use-ledger-analytics.hook'; +import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; import { - LedgerTxSigningContext, - LedgerTxSigningProvider, -} from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx.context'; -import { useActionCancellableByUser } from '@app/features/ledger/utils/stacks-ledger-utils'; + connectLedgerBitcoinApp, + getBitcoinAppVersion, + isBitcoinAppOpen, +} from '@app/features/ledger/utils/bitcoin-ledger-utils'; import { useSignLedgerBitcoinTx } from '@app/store/accounts/blockchain/bitcoin/bitcoin.hooks'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; -import { ledgerSignTxRoutes } from '../../generic-flows/tx-signing/ledger-sign-tx-route-generator'; -import { useLedgerAnalytics } from '../../hooks/use-ledger-analytics.hook'; -import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; -import { connectLedgerBitcoinApp, getBitcoinAppVersion } from '../../utils/bitcoin-ledger-utils'; -import { checkLockedDeviceError, useLedgerResponseState } from '../../utils/generic-ledger-utils'; -import { ApproveSignLedgerBitcoinTx } from './steps/approve-bitcoin-sign-ledger-tx'; - export const ledgerBitcoinTxSigningRoutes = ledgerSignTxRoutes({ component: <LedgerSignBitcoinTxContainer />, customRoutes: ( @@ -43,7 +43,6 @@ function LedgerSignBitcoinTxContainer() { const ledgerAnalytics = useLedgerAnalytics(); useScrollLock(true); - const canUserCancelAction = useActionCancellableByUser(); const [unsignedTransactionRaw, setUnsignedTransactionRaw] = useState<null | string>(null); const [unsignedTransaction, setUnsignedTransaction] = useState<null | btc.Transaction>(null); const signLedger = useSignLedgerBitcoinTx(); @@ -51,8 +50,6 @@ function LedgerSignBitcoinTxContainer() { const inputsToSign = useLocationStateWithCache<BitcoinInputSigningConfig[]>('inputsToSign'); - const allowUserToGoBack = useLocationState<boolean>('goBack'); - useEffect(() => { const tx = get(location.state, 'tx'); if (tx) { @@ -63,67 +60,51 @@ function LedgerSignBitcoinTxContainer() { useEffect(() => () => setUnsignedTransaction(null), []); - const [latestDeviceResponse, setLatestDeviceResponse] = useLedgerResponseState(); - - const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false); - - if (!inputsToSign) { - ledgerNavigate.cancelLedgerAction(); - toast.error('No input signing config defined'); - return null; - } - - const signTransaction = async () => { - setAwaitingDeviceConnection(true); - - try { - const bitcoinApp = await connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork)(); + const chain = 'bitcoin' as const; - try { - const versionInfo = await getBitcoinAppVersion(bitcoinApp); - ledgerAnalytics.trackDeviceVersionInfo(versionInfo); - setAwaitingDeviceConnection(false); - setLatestDeviceResponse(versionInfo as any); - } catch (e) { - setLatestDeviceResponse(e as any); - logger.error('Unable to get Ledger app version info', e); - } + const { signTransaction, latestDeviceResponse, awaitingDeviceConnection } = + useLedgerSignTx<BitcoinApp>({ + chain, + isAppOpen: isBitcoinAppOpen({ network: network.chain.bitcoin.bitcoinNetwork }), + getAppVersion: getBitcoinAppVersion, + connectApp: connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork), + async signTransactionWithDevice(bitcoinApp) { + if (!inputsToSign) { + ledgerNavigate.cancelLedgerAction(); + toast.error('No input signing config defined'); + return; + } - ledgerNavigate.toDeviceBusyStep('Verifying public key on Ledger…'); + ledgerNavigate.toDeviceBusyStep('Verifying public key on Ledger…'); - ledgerNavigate.toConnectionSuccessStep('bitcoin'); - await delay(1200); - if (!unsignedTransaction) throw new Error('No unsigned tx'); - - ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false }); - - try { - const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign); - - if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned'); - ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true }); + ledgerNavigate.toConnectionSuccessStep('bitcoin'); await delay(1200); - appEvents.publish('ledgerBitcoinTxSigned', { - signedPsbt: btcTx, - unsignedPsbt: unsignedTransactionRaw, - }); - } catch (e) { - logger.error('Unable to sign tx with ledger', e); - ledgerAnalytics.transactionSignedOnLedgerRejected(); - ledgerNavigate.toOperationRejectedStep(); - } finally { - void bitcoinApp.transport.close(); - } - } catch (e) { - if (e instanceof Error && checkLockedDeviceError(e)) { - setLatestDeviceResponse({ deviceLocked: true } as any); - return; - } - } - }; + if (!unsignedTransaction) throw new Error('No unsigned tx'); + + ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: false }); + + try { + const btcTx = await signLedger(bitcoinApp, unsignedTransaction.toPSBT(), inputsToSign); + + if (!btcTx || !unsignedTransactionRaw) throw new Error('No tx returned'); + ledgerNavigate.toAwaitingDeviceOperation({ hasApprovedOperation: true }); + await delay(1200); + appEvents.publish('ledgerBitcoinTxSigned', { + signedPsbt: btcTx, + unsignedPsbt: unsignedTransactionRaw, + }); + } catch (e) { + logger.error('Unable to sign tx with ledger', e); + ledgerAnalytics.transactionSignedOnLedgerRejected(); + ledgerNavigate.toOperationRejectedStep(); + } finally { + void bitcoinApp.transport.close(); + } + }, + }); const ledgerContextValue: LedgerTxSigningContext = { - chain: 'bitcoin', + chain, transaction: unsignedTransaction, signTransaction, latestDeviceResponse, @@ -131,17 +112,10 @@ function LedgerSignBitcoinTxContainer() { }; return ( - <LedgerTxSigningProvider value={ledgerContextValue}> - <BaseDrawer - enableGoBack={allowUserToGoBack} - isShowing - isWaitingOnPerformedAction={awaitingDeviceConnection || canUserCancelAction} - onClose={ledgerNavigate.cancelLedgerAction} - pauseOnClickOutside - waitingOnPerformedActionMessage="Ledger device in use" - > - <Outlet /> - </BaseDrawer> - </LedgerTxSigningProvider> + <TxSigningFlow + context={ledgerContextValue} + awaitingDeviceConnection={awaitingDeviceConnection} + closeAction={ledgerNavigate.cancelLedgerAction} + /> ); } diff --git a/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx b/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx index d7bcccf80eb..c1b718944f7 100644 --- a/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx +++ b/src/app/features/ledger/flows/jwt-signing/ledger-sign-jwt-container.tsx @@ -7,6 +7,7 @@ import get from 'lodash.get'; import { finalizeAuthResponse } from '@shared/actions/finalize-auth-response'; import { logger } from '@shared/logger'; +import { isError } from '@shared/utils'; import { useGetLegacyAuthBitcoinAddresses } from '@app/common/authentication/use-legacy-auth-bitcoin-addresses'; import { useOnboardingState } from '@app/common/hooks/auth/use-onboarding-state'; @@ -63,6 +64,8 @@ export function LedgerSignJwtContainer() { const [jwtPayloadHash, setJwtPayloadHash] = useState<null | string>(null); const { origin, tabId } = useDefaultRequestParams(); + const chain = 'stacks'; + const signJwtPayload = async () => { if (!origin) throw new Error('Cannot sign payload for unknown origin'); @@ -92,11 +95,11 @@ export function LedgerSignJwtContainer() { const stacks = await prepareLedgerDeviceStacksAppConnection({ setLoadingState: setAwaitingDeviceConnection, onError(e) { - if (e instanceof Error && checkLockedDeviceError(e)) { + if (isError(e) && checkLockedDeviceError(e)) { setLatestDeviceResponse({ deviceLocked: true } as any); return; } - ledgerNavigate.toErrorStep(); + ledgerNavigate.toErrorStep(chain); }, }); diff --git a/src/app/features/ledger/flows/request-bitcoin-keys/ledger-request-bitcoin-keys.tsx b/src/app/features/ledger/flows/request-bitcoin-keys/ledger-request-bitcoin-keys.tsx index 590ca909cf1..04806f3fc58 100644 --- a/src/app/features/ledger/flows/request-bitcoin-keys/ledger-request-bitcoin-keys.tsx +++ b/src/app/features/ledger/flows/request-bitcoin-keys/ledger-request-bitcoin-keys.tsx @@ -1,20 +1,25 @@ import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router-dom'; +import BitcoinApp from 'ledger-bitcoin'; + import { bitcoinNetworkModeToCoreNetworkMode } from '@shared/crypto/bitcoin/bitcoin.utils'; +import { pullBitcoinKeysFromLedgerDevice } from '@app/features/ledger/flows/request-bitcoin-keys/request-bitcoin-keys.utils'; +import { ledgerRequestKeysRoutes } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys-route-generator'; import { LedgerRequestKeysContext } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys.context'; +import { RequestKeysFlow } from '@app/features/ledger/generic-flows/request-keys/request-keys-flow'; +import { useRequestLedgerKeys } from '@app/features/ledger/generic-flows/request-keys/use-request-ledger-keys'; import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; +import { + connectLedgerBitcoinApp, + getBitcoinAppVersion, + isBitcoinAppOpen, +} from '@app/features/ledger/utils/bitcoin-ledger-utils'; import { useActionCancellableByUser } from '@app/features/ledger/utils/stacks-ledger-utils'; import { bitcoinKeysSlice } from '@app/store/ledger/bitcoin/bitcoin-key.slice'; import { useCurrentNetwork } from '@app/store/networks/networks.selectors'; -import { ledgerRequestKeysRoutes } from '../../generic-flows/request-keys/ledger-request-keys-route-generator'; -import { RequestKeysFlow } from '../../generic-flows/request-keys/request-keys-flow'; -import { useRequestLedgerKeys } from '../../generic-flows/request-keys/use-request-ledger-keys'; -import { connectLedgerBitcoinApp, getBitcoinAppVersion } from '../../utils/bitcoin-ledger-utils'; -import { pullBitcoinKeysFromLedgerDevice } from './request-bitcoin-keys.utils'; - function LedgerRequestBitcoinKeys() { const navigate = useNavigate(); const dispatch = useDispatch(); @@ -23,32 +28,33 @@ function LedgerRequestBitcoinKeys() { const ledgerNavigate = useLedgerNavigate(); const network = useCurrentNetwork(); - const { requestKeys, latestDeviceResponse, awaitingDeviceConnection } = useRequestLedgerKeys({ - chain: 'bitcoin', - connectApp: connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork), - getAppVersion: getBitcoinAppVersion, - isAppOpen({ name }: { name: string }) { - return name === 'Bitcoin' || name === 'Bitcoin Test'; - }, - onSuccess() { - navigate('/', { replace: true }); - }, - async pullKeysFromDevice(app) { - const { keys } = await pullBitcoinKeysFromLedgerDevice(app)({ - network: bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.bitcoinNetwork), - onRequestKey(index) { - if (index <= 4) { - ledgerNavigate.toDeviceBusyStep( - `Requesting Bitcoin Native Segwit address (${index + 1}…5)` - ); - return; - } - ledgerNavigate.toDeviceBusyStep(`Requesting Bitcoin Taproot address (${index - 4}…5)`); - }, - }); - dispatch(bitcoinKeysSlice.actions.addKeys(keys)); - }, - }); + const chain = 'bitcoin' as const; + + const { requestKeys, latestDeviceResponse, awaitingDeviceConnection } = + useRequestLedgerKeys<BitcoinApp>({ + chain, + connectApp: connectLedgerBitcoinApp(network.chain.bitcoin.bitcoinNetwork), + getAppVersion: getBitcoinAppVersion, + isAppOpen: isBitcoinAppOpen({ network: network.chain.bitcoin.bitcoinNetwork }), + onSuccess() { + navigate('/', { replace: true }); + }, + async pullKeysFromDevice(app) { + const { keys } = await pullBitcoinKeysFromLedgerDevice(app)({ + network: bitcoinNetworkModeToCoreNetworkMode(network.chain.bitcoin.bitcoinNetwork), + onRequestKey(index) { + if (index <= 4) { + ledgerNavigate.toDeviceBusyStep( + `Requesting Bitcoin Native Segwit address (${index + 1}…5)` + ); + return; + } + ledgerNavigate.toDeviceBusyStep(`Requesting Bitcoin Taproot address (${index - 4}…5)`); + }, + }); + dispatch(bitcoinKeysSlice.actions.addKeys(keys)); + }, + }); const ledgerContextValue: LedgerRequestKeysContext = { chain: 'bitcoin', diff --git a/src/app/features/ledger/flows/request-stacks-keys/ledger-request-stacks-keys.tsx b/src/app/features/ledger/flows/request-stacks-keys/ledger-request-stacks-keys.tsx index 1a42825569b..930d0ba7c3e 100644 --- a/src/app/features/ledger/flows/request-stacks-keys/ledger-request-stacks-keys.tsx +++ b/src/app/features/ledger/flows/request-stacks-keys/ledger-request-stacks-keys.tsx @@ -2,23 +2,24 @@ import toast from 'react-hot-toast'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router-dom'; +import StacksApp from '@zondax/ledger-stacks'; +import { pullStacksKeysFromLedgerDevice } from 'app/features/ledger/flows/request-stacks-keys/request-stacks-keys.utils'; + import { defaultWalletKeyId } from '@shared/utils'; +import { ledgerRequestKeysRoutes } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys-route-generator'; import { LedgerRequestKeysContext } from '@app/features/ledger/generic-flows/request-keys/ledger-request-keys.context'; +import { RequestKeysFlow } from '@app/features/ledger/generic-flows/request-keys/request-keys-flow'; +import { useRequestLedgerKeys } from '@app/features/ledger/generic-flows/request-keys/use-request-ledger-keys'; import { useLedgerNavigate } from '@app/features/ledger/hooks/use-ledger-navigate'; import { connectLedgerStacksApp, getStacksAppVersion, - isStacksLedgerAppClosed, + isStacksAppOpen, useActionCancellableByUser, } from '@app/features/ledger/utils/stacks-ledger-utils'; import { stacksKeysSlice } from '@app/store/ledger/stacks/stacks-key.slice'; -import { ledgerRequestKeysRoutes } from '../../generic-flows/request-keys/ledger-request-keys-route-generator'; -import { RequestKeysFlow } from '../../generic-flows/request-keys/request-keys-flow'; -import { useRequestLedgerKeys } from '../../generic-flows/request-keys/use-request-ledger-keys'; -import { pullStacksKeysFromLedgerDevice } from './request-stacks-keys.utils'; - function LedgerRequestStacksKeys() { const navigate = useNavigate(); const ledgerNavigate = useLedgerNavigate(); @@ -26,14 +27,14 @@ function LedgerRequestStacksKeys() { const dispatch = useDispatch(); + const chain = 'stacks'; + const { requestKeys, latestDeviceResponse, awaitingDeviceConnection, outdatedAppVersionWarning } = - useRequestLedgerKeys({ - chain: 'stacks', + useRequestLedgerKeys<StacksApp>({ + chain, connectApp: connectLedgerStacksApp, getAppVersion: getStacksAppVersion, - isAppOpen(resp) { - return !isStacksLedgerAppClosed(resp); - }, + isAppOpen: isStacksAppOpen, onSuccess() { navigate('/', { replace: true }); }, @@ -45,7 +46,7 @@ function LedgerRequestStacksKeys() { }); if (resp.status === 'failure') { toast.error(resp.errorMessage); - ledgerNavigate.toErrorStep(resp.errorMessage); + ledgerNavigate.toErrorStep(chain, resp.errorMessage); return; } ledgerNavigate.toDeviceBusyStep(); diff --git a/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx b/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx index 855814cc58c..e3b250b457e 100644 --- a/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx +++ b/src/app/features/ledger/flows/stacks-message-signing/ledger-stacks-sign-msg-container.tsx @@ -9,6 +9,7 @@ import get from 'lodash.get'; import { finalizeMessageSignature } from '@shared/actions/finalize-message-signature'; import { logger } from '@shared/logger'; import { UnsignedMessage, whenSignableMessageOfType } from '@shared/signature/signature-types'; +import { isError } from '@shared/utils'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { delay } from '@app/common/utils'; @@ -63,15 +64,17 @@ function LedgerSignStacksMsg({ account, unsignedMessage }: LedgerSignMsgProps) { const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false); + const chain = 'stacks'; + async function signMessage() { const stacksApp = await prepareLedgerDeviceStacksAppConnection({ setLoadingState: setAwaitingDeviceConnection, onError(e) { - if (e instanceof Error && checkLockedDeviceError(e)) { + if (isError(e) && checkLockedDeviceError(e)) { setLatestDeviceResponse({ deviceLocked: true } as any); return; } - ledgerNavigate.toErrorStep(); + ledgerNavigate.toErrorStep(chain); }, }); diff --git a/src/app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container.tsx b/src/app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container.tsx index a4809554c99..455f2e97640 100644 --- a/src/app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container.tsx +++ b/src/app/features/ledger/flows/stacks-tx-signing/ledger-sign-stacks-tx-container.tsx @@ -1,37 +1,33 @@ -import { useEffect, useMemo, useState } from 'react'; -import { Outlet, Route, useLocation, useNavigate } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { Route, useLocation, useNavigate } from 'react-router-dom'; import { deserializeTransaction } from '@stacks/transactions'; -import { LedgerError } from '@zondax/ledger-stacks'; +import StacksApp, { LedgerError } from '@zondax/ledger-stacks'; import get from 'lodash.get'; -import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; +import { isError } from '@shared/utils'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { appEvents } from '@app/common/publish-subscribe'; import { delay } from '@app/common/utils'; -import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { - LedgerTxSigningContext, - LedgerTxSigningProvider, - createWaitForUserToSeeWarningScreen, -} from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx.context'; +import { LedgerTxSigningContext } from '@app/features/ledger/generic-flows/tx-signing/ledger-sign-tx.context'; import { + connectLedgerStacksApp, getStacksAppVersion, + isStacksAppOpen, isVersionOfLedgerStacksAppWithContractPrincipalBug, - prepareLedgerDeviceStacksAppConnection, signLedgerStacksTransaction, signStacksTransactionWithSignature, - useActionCancellableByUser, } from '@app/features/ledger/utils/stacks-ledger-utils'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { ledgerSignTxRoutes } from '../../generic-flows/tx-signing/ledger-sign-tx-route-generator'; +import { TxSigningFlow } from '../../generic-flows/tx-signing/tx-signing-flow'; +import { useLedgerSignTx } from '../../generic-flows/tx-signing/use-ledger-sign-tx'; import { useLedgerAnalytics } from '../../hooks/use-ledger-analytics.hook'; import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; import { useVerifyMatchingLedgerStacksPublicKey } from '../../hooks/use-verify-matching-stacks-public-key'; -import { checkLockedDeviceError, useLedgerResponseState } from '../../utils/generic-ledger-utils'; import { ApproveSignLedgerStacksTx } from './steps/approve-sign-stacks-ledger-tx'; export const ledgerStacksTxSigningRoutes = ledgerSignTxRoutes({ @@ -43,16 +39,15 @@ export const ledgerStacksTxSigningRoutes = ledgerSignTxRoutes({ function LedgerSignStacksTxContainer() { const location = useLocation(); - const navigate = useNavigate(); const ledgerNavigate = useLedgerNavigate(); const ledgerAnalytics = useLedgerAnalytics(); useScrollLock(true); const account = useCurrentStacksAccount(); - const canUserCancelAction = useActionCancellableByUser(); const verifyLedgerPublicKey = useVerifyMatchingLedgerStacksPublicKey(); const [unsignedTx, setUnsignedTx] = useState<null | string>(null); + const navigate = useNavigate(); - const hasUserSkippedBuggyAppWarning = useMemo(() => createWaitForUserToSeeWarningScreen(), []); + const chain = 'stacks' as const; useEffect(() => { const tx = get(location.state, 'tx'); @@ -61,52 +56,38 @@ function LedgerSignStacksTxContainer() { useEffect(() => () => setUnsignedTx(null), []); - const [latestDeviceResponse, setLatestDeviceResponse] = useLedgerResponseState(); - - const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false); - - const signTransaction = async () => { - if (!account) return; - - const stacksApp = await prepareLedgerDeviceStacksAppConnection({ - setLoadingState: setAwaitingDeviceConnection, - onError(e) { - if (e instanceof Error && checkLockedDeviceError(e)) { - setLatestDeviceResponse({ deviceLocked: true } as any); - return; - } - ledgerNavigate.toErrorStep(); - }, - }); - - try { - const versionInfo = await getStacksAppVersion(stacksApp); - ledgerAnalytics.trackDeviceVersionInfo(versionInfo); - setLatestDeviceResponse(versionInfo); - - if (versionInfo.deviceLocked) { - setAwaitingDeviceConnection(false); - return; - } - - if (versionInfo.returnCode !== LedgerError.NoErrors) { - logger.error('Return code from device has error', versionInfo); - return; + const { + signTransaction, + latestDeviceResponse, + awaitingDeviceConnection, + hasUserSkippedBuggyAppWarning, + } = useLedgerSignTx<StacksApp>({ + chain, + isAppOpen: isStacksAppOpen, + getAppVersion: getStacksAppVersion, + connectApp: connectLedgerStacksApp, + async passesAdditionalVersionCheck(appVersion) { + if (appVersion.chain !== 'stacks') { + return true; } - if (isVersionOfLedgerStacksAppWithContractPrincipalBug(versionInfo)) { + if (isVersionOfLedgerStacksAppWithContractPrincipalBug(appVersion)) { navigate(RouteUrls.LedgerOutdatedAppWarning); const response = await hasUserSkippedBuggyAppWarning.wait(); if (response === 'cancelled-operation') { ledgerNavigate.cancelLedgerAction(); - return; } + return false; } + return true; + }, + async signTransactionWithDevice(stacksApp) { + // TODO: need better handling + if (!account) return; ledgerNavigate.toDeviceBusyStep('Verifying public key on Ledger…'); await verifyLedgerPublicKey(stacksApp); - ledgerNavigate.toConnectionSuccessStep('stacks'); await delay(1000); if (!unsignedTx) throw new Error('No unsigned tx'); @@ -148,17 +129,11 @@ function LedgerSignStacksTxContainer() { signedTx, }); } catch (e) { - ledgerNavigate.toBroadcastErrorStep(e instanceof Error ? e.message : 'Unknown error'); + ledgerNavigate.toBroadcastErrorStep(isError(e) ? e.message : 'Unknown error'); return; } - } catch (e) { - ledgerNavigate.toDeviceDisconnectStep(); - } finally { - await stacksApp.transport.close(); - } - }; - - const allowUserToGoBack = get(location.state, 'goBack'); + }, + }); function closeAction() { appEvents.publish('ledgerStacksTxSigningCancelled', { unsignedTx: unsignedTx ?? '' }); @@ -166,7 +141,7 @@ function LedgerSignStacksTxContainer() { } const ledgerContextValue: LedgerTxSigningContext = { - chain: 'stacks', + chain, transaction: unsignedTx ? deserializeTransaction(unsignedTx) : null, signTransaction, latestDeviceResponse, @@ -175,17 +150,10 @@ function LedgerSignStacksTxContainer() { }; return ( - <LedgerTxSigningProvider value={ledgerContextValue}> - <BaseDrawer - enableGoBack={allowUserToGoBack} - isShowing - isWaitingOnPerformedAction={awaitingDeviceConnection || canUserCancelAction} - onClose={closeAction} - pauseOnClickOutside - waitingOnPerformedActionMessage="Ledger device in use" - > - <Outlet /> - </BaseDrawer> - </LedgerTxSigningProvider> + <TxSigningFlow + context={ledgerContextValue} + awaitingDeviceConnection={awaitingDeviceConnection} + closeAction={closeAction} + /> ); } diff --git a/src/app/features/ledger/generic-flows/request-keys/use-request-ledger-keys.ts b/src/app/features/ledger/generic-flows/request-keys/use-request-ledger-keys.ts index 36a007db0bd..d83a9afa5ce 100644 --- a/src/app/features/ledger/generic-flows/request-keys/use-request-ledger-keys.ts +++ b/src/app/features/ledger/generic-flows/request-keys/use-request-ledger-keys.ts @@ -1,34 +1,32 @@ import { useState } from 'react'; import StacksApp from '@zondax/ledger-stacks'; -import AppClient from 'ledger-bitcoin'; +import BitcoinApp from 'ledger-bitcoin'; import { SupportedBlockchains } from '@shared/constants'; +import { isError } from '@shared/utils'; import { delay } from '@app/common/utils'; import { useLedgerAnalytics } from '../../hooks/use-ledger-analytics.hook'; import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; -import { checkLockedDeviceError, useLedgerResponseState } from '../../utils/generic-ledger-utils'; +import { BitcoinAppVersion } from '../../utils/bitcoin-ledger-utils'; +import { + LedgerConnectionErrors, + checkLockedDeviceError, + useLedgerResponseState, +} from '../../utils/generic-ledger-utils'; +import { StacksAppVersion } from '../../utils/stacks-ledger-utils'; -export enum LedgerConnectionErrors { - FailedToConnect = 'FailedToConnect', - AppNotOpen = 'AppNotOpen', - AppVersionOutdated = 'AppVersionOutdated', - DeviceNotConnected = 'DeviceNotConnected', - DeviceLocked = 'DeviceLocked', - IncorrectAppOpened = 'INCORRECT_APP_OPENED', -} - -interface UseRequestLedgerKeysArgs<App> { +interface UseRequestLedgerKeysArgs<App extends BitcoinApp | StacksApp> { chain: SupportedBlockchains; - isAppOpen(args: any): boolean; - getAppVersion(app: App): Promise<unknown>; + isAppOpen({ name }: { name: string }): boolean; + getAppVersion(app: App): Promise<StacksAppVersion> | Promise<BitcoinAppVersion>; connectApp(): Promise<App>; pullKeysFromDevice(app: App): Promise<void>; onSuccess(): void; } -export function useRequestLedgerKeys<App extends AppClient | StacksApp>({ +export function useRequestLedgerKeys<App extends BitcoinApp | StacksApp>({ chain, connectApp, getAppVersion, @@ -42,27 +40,22 @@ export function useRequestLedgerKeys<App extends AppClient | StacksApp>({ const ledgerNavigate = useLedgerNavigate(); const ledgerAnalytics = useLedgerAnalytics(); - async function connectLedgerWithFailState() { - const app = await connectApp(); - return app; - } - async function checkCorrectAppIsOpenWithFailState(app: App) { const response = await getAppVersion(app); - if (!isAppOpen(response)) { + if (!isAppOpen({ name: response.name })) { setAwaitingDeviceConnection(false); throw new Error(LedgerConnectionErrors.AppNotOpen); } return response; } - async function pullKeys() { + async function requestKeys() { let app; try { setLatestDeviceResponse({ deviceLocked: false } as any); setAwaitingDeviceConnection(true); - app = await connectLedgerWithFailState(); + app = await connectApp(); await checkCorrectAppIsOpenWithFailState(app); setAwaitingDeviceConnection(false); ledgerNavigate.toConnectionSuccessStep(chain); @@ -73,20 +66,18 @@ export function useRequestLedgerKeys<App extends AppClient | StacksApp>({ onSuccess?.(); } catch (e) { setAwaitingDeviceConnection(false); - if (e instanceof Error && checkLockedDeviceError(e)) { + if (isError(e) && checkLockedDeviceError(e)) { setLatestDeviceResponse({ deviceLocked: true } as any); return; } - ledgerNavigate.toErrorStep(); + ledgerNavigate.toErrorStep(chain); return app?.transport.close(); } } return { - async requestKeys() { - await pullKeys(); - }, + requestKeys, outdatedAppVersionWarning, setAppVersionOutdatedWarning, latestDeviceResponse, diff --git a/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx b/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx new file mode 100644 index 00000000000..ded18d87988 --- /dev/null +++ b/src/app/features/ledger/generic-flows/tx-signing/tx-signing-flow.tsx @@ -0,0 +1,38 @@ +import { Outlet } from 'react-router-dom'; + +import { useLocationState } from '@app/common/hooks/use-location-state'; +import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; +import { BaseDrawer } from '@app/components/drawer/base-drawer'; + +import { useActionCancellableByUser } from '../../utils/stacks-ledger-utils'; +import { LedgerTxSigningContext, LedgerTxSigningProvider } from './ledger-sign-tx.context'; + +interface TxSigningFlowProps { + context: LedgerTxSigningContext; + awaitingDeviceConnection: boolean; + closeAction(): void; +} +export function TxSigningFlow({ + context, + awaitingDeviceConnection, + closeAction, +}: TxSigningFlowProps) { + useScrollLock(true); + const allowUserToGoBack = useLocationState<boolean>('goBack'); + const canUserCancelAction = useActionCancellableByUser(); + + return ( + <LedgerTxSigningProvider value={context}> + <BaseDrawer + enableGoBack={allowUserToGoBack} + isShowing + isWaitingOnPerformedAction={awaitingDeviceConnection || canUserCancelAction} + onClose={closeAction} + pauseOnClickOutside + waitingOnPerformedActionMessage="Ledger device in use" + > + <Outlet /> + </BaseDrawer> + </LedgerTxSigningProvider> + ); +} diff --git a/src/app/features/ledger/generic-flows/tx-signing/use-ledger-sign-tx.ts b/src/app/features/ledger/generic-flows/tx-signing/use-ledger-sign-tx.ts new file mode 100644 index 00000000000..49f980c46f6 --- /dev/null +++ b/src/app/features/ledger/generic-flows/tx-signing/use-ledger-sign-tx.ts @@ -0,0 +1,93 @@ +import { useMemo, useState } from 'react'; + +import StacksApp from '@zondax/ledger-stacks'; +import BitcoinApp from 'ledger-bitcoin'; + +import { SupportedBlockchains } from '@shared/constants'; +import { isError } from '@shared/utils'; + +import { delay } from '@app/common/utils'; + +import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; +import { BitcoinAppVersion } from '../../utils/bitcoin-ledger-utils'; +import { + LedgerConnectionErrors, + checkLockedDeviceError, + useLedgerResponseState, +} from '../../utils/generic-ledger-utils'; +import { StacksAppVersion } from '../../utils/stacks-ledger-utils'; +import { createWaitForUserToSeeWarningScreen } from './ledger-sign-tx.context'; + +interface UseLedgerSignTxArgs<App extends BitcoinApp | StacksApp> { + chain: SupportedBlockchains; + isAppOpen({ name }: { name: string }): boolean; + getAppVersion(app: App): Promise<StacksAppVersion> | Promise<BitcoinAppVersion>; + connectApp(): Promise<App>; + passesAdditionalVersionCheck?(appVersion: StacksAppVersion | BitcoinAppVersion): Promise<boolean>; + onSuccess?(): void; + signTransactionWithDevice(app: App): Promise<void>; +} + +export function useLedgerSignTx<App extends StacksApp | BitcoinApp>({ + chain, + isAppOpen, + getAppVersion, + connectApp, + onSuccess, + signTransactionWithDevice, + passesAdditionalVersionCheck, +}: UseLedgerSignTxArgs<App>) { + const [outdatedAppVersionWarning, setAppVersionOutdatedWarning] = useState(false); + const [latestDeviceResponse, setLatestDeviceResponse] = useLedgerResponseState(); + const [awaitingDeviceConnection, setAwaitingDeviceConnection] = useState(false); + const ledgerNavigate = useLedgerNavigate(); + const hasUserSkippedBuggyAppWarning = useMemo(() => createWaitForUserToSeeWarningScreen(), []); + async function checkCorrectAppIsOpenWithFailState(app: App) { + const response = await getAppVersion(app); + if (!isAppOpen({ name: response.name })) { + setAwaitingDeviceConnection(false); + throw new Error(LedgerConnectionErrors.AppNotOpen); + } + const passedAdditionalVersionCheck = await passesAdditionalVersionCheck?.(response); + if (passedAdditionalVersionCheck) { + return response; + } + return; + } + + async function signTransactionImpl() { + let app; + try { + setLatestDeviceResponse({ deviceLocked: false } as any); + setAwaitingDeviceConnection(true); + app = await connectApp(); + await checkCorrectAppIsOpenWithFailState(app); + setAwaitingDeviceConnection(false); + ledgerNavigate.toConnectionSuccessStep(chain); + await delay(1250); + await signTransactionWithDevice(app); + onSuccess?.(); + } catch (e) { + setAwaitingDeviceConnection(false); + if (isError(e) && checkLockedDeviceError(e)) { + setLatestDeviceResponse({ deviceLocked: true } as any); + return; + } + + ledgerNavigate.toErrorStep(chain); + } finally { + await app?.transport.close(); + } + } + + return { + signTransaction: signTransactionImpl, + outdatedAppVersionWarning, + setAppVersionOutdatedWarning, + latestDeviceResponse, + setLatestDeviceResponse, + awaitingDeviceConnection, + setAwaitingDeviceConnection, + hasUserSkippedBuggyAppWarning, + }; +} diff --git a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx index 2e2c5b115ff..e9ca516154c 100644 --- a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx +++ b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.layout.tsx @@ -1,6 +1,5 @@ import { Box, Flex, HStack, Stack, styled } from 'leather-styles/jsx'; -import { capitalize } from '@app/common/utils'; import { ErrorLabel } from '@app/components/error-label'; import { WarningLabel } from '@app/components/warning-label'; import { ConnectLedgerErr } from '@app/features/ledger/illustrations/ledger-illu-connect-ledger-error'; @@ -10,7 +9,6 @@ import { Link } from '@app/ui/components/link/link'; import { LedgerTitle } from '../../components/ledger-title'; import { LedgerWrapper } from '../../components/ledger-wrapper'; -import { useLedgerRequestKeysContext } from '../../generic-flows/request-keys/ledger-request-keys.context'; interface PossibleReasonUnableToConnectProps { text: string; @@ -30,12 +28,11 @@ function PossibleReasonUnableToConnect(props: PossibleReasonUnableToConnectProps interface ConnectLedgerErrorLayoutProps { warningText: string | null; - onCancelConnectLedger(): void; onTryAgain(): void; + appName: string; } export function ConnectLedgerErrorLayout(props: ConnectLedgerErrorLayoutProps) { - const { warningText, onTryAgain } = props; - const { chain } = useLedgerRequestKeysContext(); + const { warningText, onTryAgain, appName } = props; return ( <LedgerWrapper px="space.07"> @@ -53,9 +50,7 @@ export function ConnectLedgerErrorLayout(props: ConnectLedgerErrorLayoutProps) { <Stack borderRadius="md" gap="space.01" textAlign="left" py="space.05"> <PossibleReasonUnableToConnect text="Check if Ledger Live is open. Close it and try again" /> <PossibleReasonUnableToConnect text="Ensure you only have one instance of Leather open" /> - <PossibleReasonUnableToConnect - text={`Verify the ${capitalize(chain)} app is installed and open`} - /> + <PossibleReasonUnableToConnect text={`Verify the ${appName} app is installed and open`} /> <PossibleReasonUnableToConnect text="Check you've approved the browser USB pop up" /> </Stack> <Button width="100%" onClick={onTryAgain}> diff --git a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.tsx b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.tsx index 873c586e08a..60f98360e79 100644 --- a/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.tsx +++ b/src/app/features/ledger/generic-steps/connect-device/connect-ledger-error.tsx @@ -1,3 +1,7 @@ +import { SupportedBlockchains } from '@shared/constants'; + +import { useLocationState } from '@app/common/hooks/use-location-state'; +import { capitalize } from '@app/common/utils'; import { useLatestLedgerError } from '@app/features/ledger/hooks/use-ledger-latest-route-error.hook'; import { ConnectLedgerErrorLayout } from '../../generic-steps'; @@ -6,11 +10,15 @@ import { useLedgerNavigate } from '../../hooks/use-ledger-navigate'; export function ConnectLedgerError() { const latestLedgerError = useLatestLedgerError(); const ledgerNavigate = useLedgerNavigate(); + const chain = useLocationState<SupportedBlockchains>('chain'); + // TODO: here it would be better to use + // the actual app name from LEDGER_APPS_MAP at src/app/features/ledger/utils/generic-ledger-utils.ts + const appName = capitalize(chain); return ( <ConnectLedgerErrorLayout warningText={latestLedgerError} - onCancelConnectLedger={() => ledgerNavigate.cancelLedgerAction()} + appName={appName} onTryAgain={() => ledgerNavigate.toConnectStepAndTryAgain()} /> ); diff --git a/src/app/features/ledger/hooks/use-ledger-navigate.ts b/src/app/features/ledger/hooks/use-ledger-navigate.ts index 2fe2844406c..49258117ed9 100644 --- a/src/app/features/ledger/hooks/use-ledger-navigate.ts +++ b/src/app/features/ledger/hooks/use-ledger-navigate.ts @@ -64,10 +64,10 @@ export function useLedgerNavigate() { return navigate(RouteUrls.ConnectLedgerSuccess, { replace: true, state: { chain } }); }, - toErrorStep(errorMessage?: string) { + toErrorStep(chain: SupportedBlockchains, errorMessage?: string) { return navigate(RouteUrls.ConnectLedgerError, { replace: true, - state: { latestLedgerError: errorMessage }, + state: { latestLedgerError: errorMessage, chain }, }); }, diff --git a/src/app/features/ledger/utils/bitcoin-ledger-utils.ts b/src/app/features/ledger/utils/bitcoin-ledger-utils.ts index c4201c8a677..d6319d93dfa 100644 --- a/src/app/features/ledger/utils/bitcoin-ledger-utils.ts +++ b/src/app/features/ledger/utils/bitcoin-ledger-utils.ts @@ -29,8 +29,13 @@ export function connectLedgerBitcoinApp(network: BitcoinNetworkModes) { }; } -export async function getBitcoinAppVersion(app: BitcoinApp) { - return app.getAppAndVersion(); +export interface BitcoinAppVersion extends Awaited<ReturnType<BitcoinApp['getAppAndVersion']>> { + chain: 'bitcoin'; +} + +export async function getBitcoinAppVersion(app: BitcoinApp): Promise<BitcoinAppVersion> { + const appVersion = await app.getAppAndVersion(); + return { chain: 'bitcoin' as const, ...appVersion }; } export interface WalletPolicyDetails { @@ -79,3 +84,12 @@ export function addTaprootInputSignaturesToPsbt( psbt.updateInput(index, { tapKeySig: signature.signature }) ); } + +export function isBitcoinAppOpen({ network }: { network: BitcoinNetworkModes }) { + return function isBitcoinAppOpenByName({ name }: { name: string }) { + if (network === 'mainnet') { + return name === LEDGER_APPS_MAP.BITCOIN_MAINNET; + } + return name === LEDGER_APPS_MAP.BITCOIN_TESTNET; + }; +} diff --git a/src/app/features/ledger/utils/generic-ledger-utils.ts b/src/app/features/ledger/utils/generic-ledger-utils.ts index 0e27ad286b2..ea22c6fff44 100644 --- a/src/app/features/ledger/utils/generic-ledger-utils.ts +++ b/src/app/features/ledger/utils/generic-ledger-utils.ts @@ -6,9 +6,17 @@ import BitcoinApp from 'ledger-bitcoin'; import { delay } from '@app/common/utils'; import { safeAwait } from '@app/common/utils/safe-await'; -import { LedgerConnectionErrors } from '../generic-flows/request-keys/use-request-ledger-keys'; import { getStacksAppVersion } from './stacks-ledger-utils'; +export enum LedgerConnectionErrors { + FailedToConnect = 'FailedToConnect', + AppNotOpen = 'AppNotOpen', + AppVersionOutdated = 'AppVersionOutdated', + DeviceNotConnected = 'DeviceNotConnected', + DeviceLocked = 'DeviceLocked', + IncorrectAppOpened = 'INCORRECT_APP_OPENED', +} + export const LEDGER_APPS_MAP = { STACKS: 'Stacks', BITCOIN_MAINNET: 'Bitcoin', diff --git a/src/app/features/ledger/utils/stacks-ledger-utils.ts b/src/app/features/ledger/utils/stacks-ledger-utils.ts index 63899a1895f..18cf3b1d251 100644 --- a/src/app/features/ledger/utils/stacks-ledger-utils.ts +++ b/src/app/features/ledger/utils/stacks-ledger-utils.ts @@ -44,12 +44,17 @@ export async function connectLedgerStacksApp() { return new StacksApp(transport); } -export async function getStacksAppVersion(app: StacksApp) { - const resp = await app.getVersion(); - if (resp.errorMessage !== 'No errors') { - throw new Error(resp.errorMessage); +export interface StacksAppVersion extends Awaited<ReturnType<StacksApp['getVersion']>> { + name: 'Stacks'; + chain: 'stacks'; +} + +export async function getStacksAppVersion(app: StacksApp): Promise<StacksAppVersion> { + const appVersion = await app.getVersion(); + if (appVersion.errorMessage !== 'No errors') { + throw new Error(appVersion.errorMessage); } - return { name: 'Stacks', ...resp }; + return { name: LEDGER_APPS_MAP.STACKS, chain: 'stacks' as const, ...appVersion }; } export const prepareLedgerDeviceStacksAppConnection = prepareLedgerDeviceForAppFn( @@ -110,3 +115,7 @@ export function isVersionOfLedgerStacksAppWithContractPrincipalBug( '<' ); } + +export function isStacksAppOpen({ name }: { name: string }) { + return name === LEDGER_APPS_MAP.STACKS; +} diff --git a/src/app/features/message-signer/message-signing-header.tsx b/src/app/features/message-signer/message-signing-header.tsx index 570a71f8ced..ba5794432e1 100644 --- a/src/app/features/message-signer/message-signing-header.tsx +++ b/src/app/features/message-signer/message-signing-header.tsx @@ -2,8 +2,8 @@ import { Stack, styled } from 'leather-styles/jsx'; import { addPortSuffix, getUrlHostname } from '@app/common/utils'; import { Favicon } from '@app/components/favicon'; -import { Flag } from '@app/components/layout/flag'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; +import { Flag } from '@app/ui/components/flag/flag'; interface MessageSigningHeaderProps { name?: string; @@ -32,7 +32,7 @@ export function MessageSigningHeader({ <Stack gap="space.04" pt="space.05"> <styled.h1 textStyle="heading.03">Sign message</styled.h1> {caption && ( - <Flag align="middle" img={<Favicon origin={origin ?? ''} />} pl="space.02"> + <Flag img={<Favicon origin={origin ?? ''} />} pl="space.02"> <styled.span textStyle="label.02" wordBreak="break-word"> {caption} {additionalText} diff --git a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx index 9a99ec21b4e..377777673d1 100644 --- a/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx +++ b/src/app/features/pending-brc-20-transfers/pending-brc-20-transfers.tsx @@ -6,7 +6,6 @@ import { RouteUrls } from '@shared/route-urls'; import { noop } from '@shared/utils'; import { usePressable } from '@app/components/item-hover'; -import { Flag } from '@app/components/layout/flag'; import { StatusPending } from '@app/components/status-pending'; import { StatusReady } from '@app/components/status-ready'; import { useNativeSegwitBalance } from '@app/query/bitcoin/balance/btc-native-segwit-balance.hooks'; @@ -21,6 +20,7 @@ import { usePendingBrc20Transfers, } from '@app/store/ordinals/ordinals.slice'; import { BulletSeparator } from '@app/ui/components/bullet-separator/bullet-separator'; +import { Flag } from '@app/ui/components/flag/flag'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; import { Caption } from '@app/ui/components/typography/caption'; @@ -130,12 +130,7 @@ function PendingBrcTransfer({ order }: PendingBrcTransferProps) { <HStack width="100%" mt="space.02"> <BulletSeparator> <Flex> - <Flag - ml="space.02" - align="middle" - spacing="space.02" - img={<StatusIcon status={order.status} />} - > + <Flag ml="space.02" spacing="space.02" img={<StatusIcon status={order.status} />}> <StatusLabel status={order.status} /> </Flag> </Flex> diff --git a/src/app/features/psbt-signer/components/psbt-inputs-and-outputs/components/psbt-input-output-item.layout.tsx b/src/app/features/psbt-signer/components/psbt-inputs-and-outputs/components/psbt-input-output-item.layout.tsx index 1b66056696d..e47a754a03f 100644 --- a/src/app/features/psbt-signer/components/psbt-inputs-and-outputs/components/psbt-input-output-item.layout.tsx +++ b/src/app/features/psbt-signer/components/psbt-inputs-and-outputs/components/psbt-input-output-item.layout.tsx @@ -2,7 +2,7 @@ import { Box, Flex, HStack, styled } from 'leather-styles/jsx'; import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-link'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; import { CopyIcon } from '@app/ui/components/icons/copy-icon'; import { Link } from '@app/ui/components/link/link'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; @@ -27,7 +27,7 @@ export function PsbtInputOutputItemLayout({ const { handleOpenBitcoinTxLink: handleOpenTxLink } = useBitcoinExplorerLink(); return ( - <Flag align="middle" img={<></>} mt="space.05" spacing="space.04"> + <Flag img={<></>} mt="space.05" spacing="space.04"> <HStack alignItems="center" justifyContent="space-between"> <Flex alignItems="center"> <styled.span mr="space.02" textStyle="caption.01"> diff --git a/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx b/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx index 3c12fa02a4c..7ecd1af3eae 100644 --- a/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx +++ b/src/app/features/psbt-signer/components/psbt-inputs-outputs-totals/components/psbt-address-total-item.tsx @@ -1,7 +1,7 @@ import { Box, HStack, styled } from 'leather-styles/jsx'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; import { BtcIcon } from '@app/ui/components/icons/btc-icon'; import { CopyIcon } from '@app/ui/components/icons/copy-icon'; import { Link } from '@app/ui/components/link/link'; @@ -28,7 +28,7 @@ export function PsbtAddressTotalItem({ const { onCopy, hasCopied } = useClipboard(hoverLabel ?? ''); return ( - <Flag align="middle" img={image ? image : <BtcIcon />} mt="space.05" spacing="space.04"> + <Flag img={image ? image : <BtcIcon />} mt="space.05" spacing="space.04"> <HStack alignItems="center" justifyContent="space-between"> <styled.span textStyle="label.01">{title ? title : 'Bitcoin'}</styled.span> {valueAction ? ( diff --git a/src/app/features/psbt-signer/components/psbt-request-header.tsx b/src/app/features/psbt-signer/components/psbt-request-header.tsx index 78bb0230f8b..29ee9c44328 100644 --- a/src/app/features/psbt-signer/components/psbt-request-header.tsx +++ b/src/app/features/psbt-signer/components/psbt-request-header.tsx @@ -1,7 +1,7 @@ import { Flex, styled } from 'leather-styles/jsx'; import { Favicon } from '@app/components/favicon'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; interface PsbtRequestHeaderProps { name?: string; @@ -23,7 +23,7 @@ export function PsbtRequestHeader({ name, origin }: PsbtRequestHeaderProps) { transactions you fully understand. </styled.p> {caption && ( - <Flag align="middle" img={<Favicon origin={origin} />} pl="space.02"> + <Flag img={<Favicon origin={origin} />} pl="space.02"> <styled.span textStyle="label.02" wordBreak="break-word"> {caption} </styled.span> diff --git a/src/app/features/psbt-signer/psbt-signer.tsx b/src/app/features/psbt-signer/psbt-signer.tsx index 0b59788e404..85dc31a1fdb 100644 --- a/src/app/features/psbt-signer/psbt-signer.tsx +++ b/src/app/features/psbt-signer/psbt-signer.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { getPsbtTxInputs, getPsbtTxOutputs } from '@shared/crypto/bitcoin/bitcoin.utils'; import { RouteUrls } from '@shared/route-urls'; -import { closeWindow } from '@shared/utils'; +import { closeWindow, isError } from '@shared/utils'; import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { SignPsbtArgs } from '@app/common/psbt/requests'; @@ -51,7 +51,7 @@ export function PsbtSigner(props: PsbtSignerProps) { return getRawPsbt(psbtHex); } catch (e) { navigate(RouteUrls.RequestError, { - state: { message: e instanceof Error ? e.message : '', title: 'Failed request' }, + state: { message: isError(e) ? e.message : '', title: 'Failed request' }, }); return; } diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-broadcast-transaction.tsx b/src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx similarity index 84% rename from src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-broadcast-transaction.tsx rename to src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx index 028340d1932..3aaf8850c3f 100644 --- a/src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-broadcast-transaction.tsx +++ b/src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx @@ -2,12 +2,12 @@ import { useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import { useNavigate } from 'react-router-dom'; -import { StacksTransaction, deserializeTransaction } from '@stacks/transactions'; +import { StacksTransaction } from '@stacks/transactions'; import { logger } from '@shared/logger'; import { CryptoCurrencies } from '@shared/models/currencies.model'; import { RouteUrls } from '@shared/route-urls'; -import { isString } from '@shared/utils'; +import { isError, isString } from '@shared/utils'; import { LoadingKeys } from '@app/common/hooks/use-loading'; import { useSubmitTransactionCallback } from '@app/common/hooks/use-submit-stx-transaction'; @@ -15,11 +15,7 @@ import { useSignStacksTransaction } from '@app/store/transactions/transaction.ho import { useStacksTransactionSummary } from './use-stacks-transaction-summary'; -export function useStacksBroadcastTransaction( - unsignedTx: string, - token: CryptoCurrencies, - decimals?: number -) { +export function useStacksBroadcastTransaction(token: CryptoCurrencies, decimals?: number) { const signStacksTransaction = useSignStacksTransaction(); const [isBroadcasting, setIsBroadcasting] = useState(false); const { formSentSummaryTxState } = useStacksTransactionSummary(token); @@ -60,7 +56,7 @@ export function useStacksBroadcastTransaction( })(signedTx); } catch (e) { navigate(RouteUrls.TransactionBroadcastError, { - state: { message: e instanceof Error ? e.message : 'Unknown error' }, + state: { message: isError(e) ? e.message : 'Unknown error' }, }); } finally { setIsBroadcasting(false); @@ -76,10 +72,7 @@ export function useStacksBroadcastTransaction( } catch (e) {} } - const deserializedTransaction = deserializeTransaction(unsignedTx); - return { - stacksDeserializedTransaction: deserializedTransaction, stacksBroadcastTransaction: broadcastTransaction, isBroadcasting, }; @@ -87,7 +80,6 @@ export function useStacksBroadcastTransaction( broadcastTransactionFn, navigate, signStacksTransaction, - unsignedTx, isBroadcasting, token, formSentSummaryTxState, diff --git a/src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-transaction-summary.ts b/src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts similarity index 100% rename from src/app/pages/send/send-crypto-asset-form/family/stacks/hooks/use-stacks-transaction-summary.ts rename to src/app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary.ts diff --git a/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts b/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts index 41f0493d41a..e79e95cbba4 100644 --- a/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts +++ b/src/app/features/stacks-transaction-request/hooks/use-transaction-error.ts @@ -11,7 +11,7 @@ import { initialSearchParams } from '@app/common/initial-search-params'; import { stxToMicroStx } from '@app/common/money/unit-conversion'; import { validateStacksAddress } from '@app/common/stacks-utils'; import { TransactionErrorReason } from '@app/features/stacks-transaction-request/transaction-error/transaction-error'; -import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useContractInterface } from '@app/query/stacks/contract/contract.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; @@ -27,7 +27,7 @@ export function useTransactionError() { const { values } = useFormikContext<StacksTransactionFormValues>(); const currentAccount = useCurrentStacksAccount(); - const { data: balances } = useCurrentStacksAccountAnchoredBalances(); + const { data: balances } = useCurrentStacksAccountBalances(); return useMemo<TransactionErrorReason | void>(() => { if (!origin) return TransactionErrorReason.ExpiredRequest; diff --git a/src/app/features/stacks-transaction-request/page-top.tsx b/src/app/features/stacks-transaction-request/page-top.tsx index e5985829756..8b3a8d5fb63 100644 --- a/src/app/features/stacks-transaction-request/page-top.tsx +++ b/src/app/features/stacks-transaction-request/page-top.tsx @@ -6,10 +6,10 @@ import { Stack, styled } from 'leather-styles/jsx'; import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params'; import { addPortSuffix, getUrlHostname } from '@app/common/utils'; import { Favicon } from '@app/components/favicon'; -import { Flag } from '@app/components/layout/flag'; import { useStacksTxPageTitle } from '@app/features/stacks-transaction-request/hooks/use-stacks-tx-page-title'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; +import { Flag } from '@app/ui/components/flag/flag'; function PageTopBase() { const transactionRequest = useTransactionRequestState(); @@ -37,7 +37,7 @@ function PageTopBase() { {pageTitle} </styled.h1> {caption && ( - <Flag align="middle" img={<Favicon origin={origin ?? ''} />} pl="space.02"> + <Flag img={<Favicon origin={origin ?? ''} />} pl="space.02"> <styled.span textStyle="label.02" wordBreak="break-word"> {caption} </styled.span> diff --git a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx index bce6c6504b2..aa1207aaccf 100644 --- a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx +++ b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx @@ -27,7 +27,7 @@ import { PostConditionModeWarning } from '@app/features/stacks-transaction-reque import { PostConditions } from '@app/features/stacks-transaction-request/post-conditions/post-conditions'; import { StxTransferDetails } from '@app/features/stacks-transaction-request/stx-transfer-details/stx-transfer-details'; import { TransactionError } from '@app/features/stacks-transaction-request/transaction-error/transaction-error'; -import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; @@ -55,7 +55,7 @@ export function StacksTransactionSigner({ const transactionRequest = useTransactionRequestState(); const { data: stxFees } = useCalculateStacksTxFees(stacksTransaction); const analytics = useAnalytics(); - const { data: stacksBalances } = useCurrentStacksAccountAnchoredBalances(); + const { data: stacksBalances } = useCurrentStacksAccountBalances(); const navigate = useNavigate(); const { data: nextNonce } = useNextNonce(); const { search } = useLocation(); diff --git a/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx b/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx index dd8227d7680..374d70aa006 100644 --- a/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx +++ b/src/app/features/stacks-transaction-request/transaction-error/error-messages.tsx @@ -12,7 +12,7 @@ import { useDrawers } from '@app/common/hooks/use-drawers'; import { useScrollLock } from '@app/common/hooks/use-scroll-lock'; import { stacksValue } from '@app/common/stacks-utils'; import { ErrorMessage } from '@app/features/stacks-transaction-request/transaction-error/error-message'; -import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; import { Button } from '@app/ui/components/button/button'; @@ -55,7 +55,7 @@ export const FeeInsufficientFundsErrorMessage = memo(props => { export const StxTransferInsufficientFundsErrorMessage = memo(props => { const pendingTransaction = useTransactionRequestState(); - const { data: balance } = useCurrentStacksAccountAnchoredBalances(); + const { data: balance } = useCurrentStacksAccountBalances(); return ( <ErrorMessage diff --git a/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx b/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx index 761823e7bc1..384fb50567c 100644 --- a/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx +++ b/src/app/features/switch-account-drawer/components/switch-account-list-item.tsx @@ -4,13 +4,13 @@ import { useAccountDisplayName } from '@app/common/hooks/account/use-account-nam import { useSwitchAccount } from '@app/common/hooks/account/use-switch-account'; import { useLoading } from '@app/common/hooks/use-loading'; import { AccountTotalBalance } from '@app/components/account-total-balance'; -import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; -import { usePressable } from '@app/components/item-hover'; +import { AcccountAddresses } from '@app/components/account/account-addresses'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; +import { AccountNameLayout } from '@app/components/account/account-name'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { AccountAvatarItem } from '../../../components/account/account-avatar-item'; -import { AccountNameLayout } from '../../../components/account/account-name'; interface SwitchAccountListItemProps { handleClose(): void; @@ -20,16 +20,15 @@ interface SwitchAccountListItemProps { export const SwitchAccountListItem = memo( ({ handleClose, currentAccountIndex, index }: SwitchAccountListItemProps) => { const stacksAccounts = useStacksAccounts(); - const stacksAddress = stacksAccounts[index]?.address || ''; + const stxAddress = stacksAccounts[index]?.address || ''; const bitcoinSigner = useNativeSegwitSigner(index); - const bitcoinAddress = bitcoinSigner?.(0).address || ''; + const btcAddress = bitcoinSigner?.(0).address || ''; const { isLoading, setIsLoading, setIsIdle } = useLoading( - 'SWITCH_ACCOUNTS' + stacksAddress || bitcoinAddress + 'SWITCH_ACCOUNTS' + stxAddress || btcAddress ); const { handleSwitchAccount } = useSwitchAccount(handleClose); - const [component, bind] = usePressable(true); - const name = useAccountDisplayName({ address: stacksAddress, index }); + const name = useAccountDisplayName({ address: stxAddress, index }); const handleClick = async () => { setIsLoading(); @@ -41,9 +40,8 @@ export const SwitchAccountListItem = memo( return ( <AccountListItemLayout - index={index} - isLoading={isLoading} - isActive={currentAccountIndex === index} + accountAddresses={<AcccountAddresses index={index} />} + accountName={<AccountNameLayout>{name}</AccountNameLayout>} avatar={ <AccountAvatarItem index={index} @@ -51,16 +49,12 @@ export const SwitchAccountListItem = memo( name={name} /> } + balanceLabel={<AccountTotalBalance stxAddress={stxAddress} btcAddress={btcAddress} />} + index={index} + isLoading={isLoading} + isSelected={currentAccountIndex === index} onSelectAccount={handleClick} - accountName={<AccountNameLayout>{name}</AccountNameLayout>} - balanceLabel={ - <AccountTotalBalance stxAddress={stacksAddress} btcAddress={bitcoinAddress} /> - } - mt="space.05" - {...bind} - > - {component} - </AccountListItemLayout> + /> ); } ); diff --git a/src/app/features/switch-account-drawer/components/switch-account-list.tsx b/src/app/features/switch-account-drawer/components/switch-account-list.tsx index 809dccd3804..0cafce7570f 100644 --- a/src/app/features/switch-account-drawer/components/switch-account-list.tsx +++ b/src/app/features/switch-account-drawer/components/switch-account-list.tsx @@ -23,7 +23,7 @@ export const SwitchAccountList = memo( style={{ paddingTop: '24px', height: '70vh' }} totalCount={addressesNum} itemContent={index => ( - <Box mx={['space.05', 'space.06']} key={index}> + <Box key={index} mx="space.05"> <SwitchAccountListItem handleClose={handleClose} currentAccountIndex={currentAccountIndex} diff --git a/src/app/features/switch-account-drawer/switch-account-drawer.tsx b/src/app/features/switch-account-drawer/switch-account-drawer.tsx index 6fa56009a0f..765fdef4070 100644 --- a/src/app/features/switch-account-drawer/switch-account-drawer.tsx +++ b/src/app/features/switch-account-drawer/switch-account-drawer.tsx @@ -39,7 +39,7 @@ export const SwitchAccountDrawer = memo(() => { return isShowing ? ( <ControlledDrawer title="Select account" isShowing={isShowing} onClose={onClose}> - <Box mb={whenWallet({ ledger: 'space.04', software: '' })}> + <Box mb={whenWallet({ ledger: 'space.04', software: '' })} pb="space.09"> <SwitchAccountList currentAccountIndex={currentAccountIndex} handleClose={onClose} diff --git a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx index ff771e8b077..eef46c7d484 100644 --- a/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx +++ b/src/app/pages/bitcoin-contract-list/components/bitcoin-contract-list-item-layout.tsx @@ -8,8 +8,8 @@ import { useBitcoinExplorerLink } from '@app/common/hooks/use-bitcoin-explorer-l import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money'; import { i18nFormatCurrency } from '@app/common/money/format-money'; import { satToBtc } from '@app/common/money/unit-conversion'; -import { Flag } from '@app/components/layout/flag'; import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks'; +import { Flag } from '@app/ui/components/flag/flag'; import { BitcoinContractIcon } from '@app/ui/components/icons/bitcoin-contract-icon'; import { Caption } from '@app/ui/components/typography/caption'; @@ -45,7 +45,7 @@ export function BitcoinContractListItemLayout({ }) } > - <Flag img={<BitcoinContractIcon />} align="middle" spacing="space.04" width="100%"> + <Flag img={<BitcoinContractIcon />} spacing="space.04" width="100%"> <HStack alignItems="center" justifyContent="space-between" width="100%"> <styled.span textStyle="body.01">{id}</styled.span> <styled.span fontVariantNumeric="tabular-nums" textAlign="right" textStyle="body.01"> diff --git a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-offer/bitcoin-contract-lock-amount.tsx b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-offer/bitcoin-contract-lock-amount.tsx index cc1cd5c6bcc..723974a1883 100644 --- a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-offer/bitcoin-contract-lock-amount.tsx +++ b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-offer/bitcoin-contract-lock-amount.tsx @@ -5,7 +5,7 @@ import { HStack, styled } from 'leather-styles/jsx'; import { token } from 'leather-styles/tokens'; import { useClipboard } from '@app/common/hooks/use-copy-to-clipboard'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; import { ArrowUpIcon } from '@app/ui/components/icons/arrow-up-icon'; import { BtcIcon } from '@app/ui/components/icons/btc-icon'; import { CopyIcon } from '@app/ui/components/icons/copy-icon'; @@ -33,7 +33,7 @@ export function BitcoinContractLockAmount({ const { onCopy, hasCopied } = useClipboard(hoverLabel ?? ''); return ( - <Flag align="middle" img={image || <BtcIcon />} width="100%"> + <Flag img={image || <BtcIcon />} width="100%"> <HStack alignItems="center" justifyContent="space-between"> <styled.span textStyle="label.01">{title ? title : 'BTC'}</styled.span> <styled.span diff --git a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-header.tsx b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-header.tsx index 65247be0b40..cf32f67a147 100644 --- a/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-header.tsx +++ b/src/app/pages/bitcoin-contract-request/components/bitcoin-contract-request-header.tsx @@ -3,7 +3,7 @@ import { memo } from 'react'; import { BitcoinContractRequestSelectors } from '@tests/selectors/bitcoin-contract-request.selectors'; import { Flex } from 'leather-styles/jsx'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; import { Caption } from '@app/ui/components/typography/caption'; import { Title } from '@app/ui/components/typography/title'; @@ -22,11 +22,7 @@ function BitcoinContractRequestHeaderBase({ <Flex flexDirection="column" my="space.05" width="100%"> <Title mb="space.04">Lock Bitcoin {caption && ( - } - pl="space.02" - > + } pl="space.02"> {caption} diff --git a/src/app/pages/choose-account/components/accounts.tsx b/src/app/pages/choose-account/components/accounts.tsx index ac452e9d973..793396e2589 100644 --- a/src/app/pages/choose-account/components/accounts.tsx +++ b/src/app/pages/choose-account/components/accounts.tsx @@ -12,8 +12,9 @@ import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; import { useWalletType } from '@app/common/use-wallet-type'; import { slugify } from '@app/common/utils'; import { AccountTotalBalance } from '@app/components/account-total-balance'; +import { AcccountAddresses } from '@app/components/account/account-addresses'; import { AccountAvatar } from '@app/components/account/account-avatar'; -import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { usePressable } from '@app/components/item-hover'; import { useNativeSegwitAccountIndexAddressIndexZero } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -36,19 +37,16 @@ interface ChooseAccountItemProps extends FlexProps { } const ChooseAccountItem = memo( ({ account, isLoading, onSelectAccount }: ChooseAccountItemProps) => { - const [component, bind] = usePressable(true); const name = useAccountDisplayName(account); const btcAddress = useNativeSegwitAccountIndexAddressIndexZero(account.index); const accountSlug = useMemo(() => slugify(`Account ${account?.index + 1}`), [account?.index]); return ( - // Padding required on outer element to prevent jumpy list behaviours in - // virtualised list library + // Padding required on outer element to prevent jumpy virtualized list } accountName={ }> {name} @@ -65,13 +63,12 @@ const ChooseAccountItem = memo( balanceLabel={ } + data-testid={`account-${accountSlug}-${account.index}`} + index={account.index} isLoading={isLoading} + isSelected={false} onSelectAccount={() => onSelectAccount(account.index)} - data-testid={`account-${accountSlug}-${account.index}`} - {...bind} - > - {component} - + /> ); } diff --git a/src/app/pages/home/components/send-button.tsx b/src/app/pages/home/components/send-button.tsx index 74bf9d04f2b..3017d57b5b6 100644 --- a/src/app/pages/home/components/send-button.tsx +++ b/src/app/pages/home/components/send-button.tsx @@ -9,7 +9,7 @@ import { useWalletType } from '@app/common/use-wallet-type'; import { whenPageMode } from '@app/common/utils'; import { openIndexPageInNewTab } from '@app/common/utils/open-in-new-tab'; import { - useStacksAnchoredCryptoCurrencyAssetBalance, + useStacksCryptoCurrencyAssetBalance, useTransferableStacksFungibleTokenAssetBalances, } from '@app/query/stacks/balance/stacks-ft-balances.hooks'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; @@ -21,7 +21,7 @@ function SendButtonSuspense() { const navigate = useNavigate(); const { whenWallet } = useWalletType(); const account = useCurrentStacksAccount(); - const stxAssetBalance = useStacksAnchoredCryptoCurrencyAssetBalance(account?.address ?? ''); + const stxAssetBalance = useStacksCryptoCurrencyAssetBalance(account?.address ?? ''); const ftAssets = useTransferableStacksFungibleTokenAssetBalances(account?.address ?? ''); const isDisabled = !stxAssetBalance && ftAssets?.length === 0; diff --git a/src/app/pages/psbt-request/use-psbt-request.tsx b/src/app/pages/psbt-request/use-psbt-request.tsx index 144e46adf37..8eb6e280f1a 100644 --- a/src/app/pages/psbt-request/use-psbt-request.tsx +++ b/src/app/pages/psbt-request/use-psbt-request.tsx @@ -5,6 +5,7 @@ import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { finalizePsbt } from '@shared/actions/finalize-psbt'; import { RouteUrls } from '@shared/route-urls'; +import { isError } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { usePsbtRequestSearchParams } from '@app/common/psbt/use-psbt-request-params'; @@ -57,7 +58,7 @@ export function usePsbtRequest() { }); } catch (e) { return navigate(RouteUrls.RequestError, { - state: { message: e instanceof Error ? e.message : '', title: 'Failed to sign' }, + state: { message: isError(e) ? e.message : '', title: 'Failed to sign' }, }); } }, diff --git a/src/app/pages/receive/components/receive-item.tsx b/src/app/pages/receive/components/receive-item.tsx index 95d97cec82c..d4b79796505 100644 --- a/src/app/pages/receive/components/receive-item.tsx +++ b/src/app/pages/receive/components/receive-item.tsx @@ -1,9 +1,8 @@ -import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; - -import { Flag } from '@app/components/layout/flag'; import { Button } from '@app/ui/components/button/button'; import { CopyIcon } from '@app/ui/components/icons/copy-icon'; import { QrCodeIcon } from '@app/ui/components/icons/qr-code-icon'; +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemWithButtonsLayout } from '@app/ui/components/item/item-with-buttons.layout'; import { truncateMiddle } from '@app/ui/utils/truncate-middle'; interface ReceiveItemProps { @@ -24,23 +23,29 @@ export function ReceiveItem({ }: ReceiveItemProps) { if (!address) return null; return ( - - - - {title} - {truncateMiddle(address, 6)} - - - - {onClickQrCode && ( - - )} - - - + {onClickQrCode && ( + + )} + + } + /> + ); } diff --git a/src/app/pages/receive/components/receive-items.tsx b/src/app/pages/receive/components/receive-items.tsx index d1c2d674ae9..4f89ecee966 100644 --- a/src/app/pages/receive/components/receive-items.tsx +++ b/src/app/pages/receive/components/receive-items.tsx @@ -13,7 +13,7 @@ export function ReceiveItemList({ children, title }: ReceiveItemListProps) { )} - + {children} diff --git a/src/app/pages/rpc-send-transfer/components/send-transfer-header.tsx b/src/app/pages/rpc-send-transfer/components/send-transfer-header.tsx index 57cf469ac9e..8aedf76c30e 100644 --- a/src/app/pages/rpc-send-transfer/components/send-transfer-header.tsx +++ b/src/app/pages/rpc-send-transfer/components/send-transfer-header.tsx @@ -1,7 +1,7 @@ import { Flex, styled } from 'leather-styles/jsx'; import { Favicon } from '@app/components/favicon'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; interface SendTransferHeaderProps { amount: string; @@ -17,7 +17,7 @@ export function SendTransferHeader({ amount, origin }: SendTransferHeaderProps) {title} {caption && ( - } pl="space.02"> + } pl="space.02"> {caption} diff --git a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx index 4c43efa4b8e..222f4033027 100644 --- a/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx +++ b/src/app/pages/rpc-sign-psbt/use-rpc-sign-psbt.tsx @@ -7,7 +7,7 @@ import { bytesToHex } from '@stacks/common'; import { Money } from '@shared/models/money.model'; import { RouteUrls } from '@shared/route-urls'; import { makeRpcErrorResponse, makeRpcSuccessResponse } from '@shared/rpc/rpc-methods'; -import { closeWindow } from '@shared/utils'; +import { closeWindow, isError } from '@shared/utils'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { sumMoney } from '@app/common/money/calculate-money'; @@ -78,7 +78,7 @@ export function useRpcSignPsbt() { }, onError(e) { navigate(RouteUrls.RequestError, { - state: { message: e instanceof Error ? e.message : '', title: 'Failed to broadcast' }, + state: { message: isError(e) ? e.message : '', title: 'Failed to broadcast' }, }); }, }); @@ -113,7 +113,7 @@ export function useRpcSignPsbt() { } catch (e) { return navigate(RouteUrls.RequestError, { state: { - message: e instanceof Error ? e.message : '', + message: isError(e) ? e.message : '', title: 'Failed to finalize tx', }, }); @@ -130,7 +130,7 @@ export function useRpcSignPsbt() { closeWindow(); } catch (e) { return navigate(RouteUrls.RequestError, { - state: { message: e instanceof Error ? e.message : '', title: 'Failed to sign' }, + state: { message: isError(e) ? e.message : '', title: 'Failed to sign' }, }); } }, diff --git a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx index 5aa4859b6b8..f60660bf7d2 100644 --- a/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx +++ b/src/app/pages/send/ordinal-inscription/hooks/use-send-inscription-form.tsx @@ -6,6 +6,7 @@ import * as yup from 'yup'; import { logger } from '@shared/logger'; import { OrdinalSendFormValues } from '@shared/models/form.model'; import { RouteUrls } from '@shared/route-urls'; +import { isError } from '@shared/utils'; import { FormErrorMessages } from '@app/common/error-messages'; import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; @@ -66,7 +67,7 @@ export function useSendInscriptionForm() { void analytics.track('ordinals_dot_com_unavailable', { error }); let message = 'Unable to establish if utxo has multiple inscriptions'; - if (error instanceof Error) { + if (isError(error)) { message = error.message; } setShowError(message); diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx index 971e0aa5bb5..d59114ed9d0 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-accounts-drawer/account-list-item.tsx @@ -6,10 +6,10 @@ import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { AccountTotalBalance } from '@app/components/account-total-balance'; +import { AcccountAddresses } from '@app/components/account/account-addresses'; import { AccountAvatarItem } from '@app/components/account/account-avatar-item'; -import { AccountListItemLayout } from '@app/components/account/account-list-item-layout'; +import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { AccountNameLayout } from '@app/components/account/account-name'; -import { usePressable } from '@app/components/item-hover'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; @@ -22,7 +22,6 @@ export const AccountListItem = memo(({ index, stacksAccount, onClose }: AccountL const { setFieldValue, values, setFieldTouched } = useFormikContext< BitcoinSendFormValues | StacksSendFormValues >(); - const [component, bind] = usePressable(true); const stacksAddress = stacksAccount?.address || ''; const name = useAccountDisplayName({ address: stacksAddress, index }); @@ -38,6 +37,7 @@ export const AccountListItem = memo(({ index, stacksAccount, onClose }: AccountL return ( } accountName={{name}} avatar={ } index={index} - isActive={false} + isSelected={false} isLoading={false} - mt="space.05" onSelectAccount={onSelectAccount} - {...bind} - > - {component} - + /> ); }); diff --git a/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/hooks/use-recipient-bns-name.tsx b/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/hooks/use-recipient-bns-name.tsx index 3afec17cf07..1d6c20cefb5 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/hooks/use-recipient-bns-name.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/recipient-fields/hooks/use-recipient-bns-name.tsx @@ -7,7 +7,7 @@ import { BitcoinSendFormValues, StacksSendFormValues } from '@shared/models/form import { FormErrorMessages } from '@app/common/error-messages'; import { StacksClient } from '@app/query/stacks/stacks-client'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; // Handles validating the BNS name lookup @@ -17,7 +17,7 @@ export function useRecipientBnsName() { >(); const [bnsAddress, setBnsAddress] = useState(''); const currentNetwork = useCurrentNetworkState(); - const client = useStacksClientUnanchored(); + const client = useStacksClient(); const getBnsAddressAndValidate = useCallback( async ( diff --git a/src/app/pages/send/send-crypto-asset-form/components/selected-asset-field.tsx b/src/app/pages/send/send-crypto-asset-form/components/selected-asset-field.tsx index 63981ffa383..a01449550fb 100644 --- a/src/app/pages/send/send-crypto-asset-form/components/selected-asset-field.tsx +++ b/src/app/pages/send/send-crypto-asset-form/components/selected-asset-field.tsx @@ -2,7 +2,7 @@ import { Field, useField } from 'formik'; import { Flex, styled } from 'leather-styles/jsx'; import { useOnMount } from '@app/common/hooks/use-on-mount'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; interface SelectedAssetFieldProps { icon: React.JSX.Element; @@ -26,7 +26,7 @@ export function SelectedAssetField({ icon, name, symbol }: SelectedAssetFieldPro width="100%" > - + {name} diff --git a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx index 87b99531a8b..8be0a7d5638 100644 --- a/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx +++ b/src/app/pages/send/send-crypto-asset-form/form/stacks/stacks-send-form-confirmation.tsx @@ -1,5 +1,6 @@ import { Outlet, useNavigate, useParams } from 'react-router-dom'; +import { deserializeTransaction } from '@stacks/transactions'; import { Box, Stack } from 'leather-styles/jsx'; import { CryptoCurrencies } from '@shared/models/currencies.model'; @@ -7,11 +8,11 @@ import { CryptoCurrencies } from '@shared/models/currencies.model'; import { useLocationStateWithCache } from '@app/common/hooks/use-location-state'; import { useRouteHeader } from '@app/common/hooks/use-route-header'; import { ModalHeader } from '@app/components/modal-header'; +import { useStacksBroadcastTransaction } from '@app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction'; +import { useStacksTransactionSummary } from '@app/features/stacks-transaction-request/hooks/use-stacks-transaction-summary'; import { InfoIcon } from '@app/ui/components/icons/info-icon'; import { BasicTooltip } from '@app/ui/components/tooltip/basic-tooltip'; -import { useStacksBroadcastTransaction } from '../../family/stacks/hooks/use-stacks-broadcast-transaction'; -import { useStacksTransactionSummary } from '../../family/stacks/hooks/use-stacks-transaction-summary'; import { SendFormConfirmation } from '../send-form-confirmation'; function useStacksSendFormConfirmationState() { @@ -27,8 +28,12 @@ export function StacksSendFormConfirmation() { const { symbol = 'STX' } = useParams(); const navigate = useNavigate(); - const { stacksDeserializedTransaction, stacksBroadcastTransaction, isBroadcasting } = - useStacksBroadcastTransaction(tx, symbol.toUpperCase() as CryptoCurrencies, decimals); + const { stacksBroadcastTransaction, isBroadcasting } = useStacksBroadcastTransaction( + symbol.toUpperCase() as CryptoCurrencies, + decimals + ); + + const stacksDeserializedTransaction = deserializeTransaction(tx); const { formReviewTxSummary } = useStacksTransactionSummary( symbol.toUpperCase() as CryptoCurrencies diff --git a/src/app/pages/sign-out-confirm/sign-out-confirm.layout.tsx b/src/app/pages/sign-out-confirm/sign-out-confirm.layout.tsx index c41728aaf67..b0d7955c3ae 100644 --- a/src/app/pages/sign-out-confirm/sign-out-confirm.layout.tsx +++ b/src/app/pages/sign-out-confirm/sign-out-confirm.layout.tsx @@ -5,8 +5,8 @@ import { Box, HStack, styled } from 'leather-styles/jsx'; import { useThemeSwitcher } from '@app/common/theme-provider'; import { useWalletType } from '@app/common/use-wallet-type'; import { BaseDrawer } from '@app/components/drawer/base-drawer'; -import { Flag } from '@app/components/layout/flag'; import { Button } from '@app/ui/components/button/button'; +import { Flag } from '@app/ui/components/flag/flag'; import { ErrorIcon } from '@app/ui/components/icons/error-icon'; interface SignOutConfirmLayoutProps { @@ -43,7 +43,7 @@ export function SignOutConfirmLayout(props: SignOutConfirmLayoutProps) { {whenWallet({ software: ( - } spacing="space.02"> + } spacing="space.02"> If you haven't backed up your Secret Key, you will lose all your funds. ), diff --git a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx index b82408b71c7..c1c388a7843 100644 --- a/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx +++ b/src/app/pages/swap/components/swap-assets-pair/swap-asset-item.layout.tsx @@ -1,7 +1,7 @@ import { SwapSelectors } from '@tests/selectors/swap.selectors'; import { HStack, styled } from 'leather-styles/jsx'; -import { Flag } from '@app/components/layout/flag'; +import { Flag } from '@app/ui/components/flag/flag'; interface SwapAssetItemLayoutProps { caption: string; @@ -12,7 +12,6 @@ interface SwapAssetItemLayoutProps { export function SwapAssetItemLayout({ caption, icon, symbol, value }: SwapAssetItemLayoutProps) { return ( } spacing="space.03" width="100%" diff --git a/src/app/pages/swap/hooks/use-stacks-broadcast-swap.tsx b/src/app/pages/swap/hooks/use-stacks-broadcast-swap.tsx index 551eb4598b3..4046cd2ec20 100644 --- a/src/app/pages/swap/hooks/use-stacks-broadcast-swap.tsx +++ b/src/app/pages/swap/hooks/use-stacks-broadcast-swap.tsx @@ -6,7 +6,7 @@ import { StacksTransaction } from '@stacks/transactions'; import { logger } from '@shared/logger'; import { RouteUrls } from '@shared/route-urls'; -import { isString } from '@shared/utils'; +import { isError, isString } from '@shared/utils'; import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { useSubmitTransactionCallback } from '@app/common/hooks/use-submit-stx-transaction'; @@ -42,7 +42,7 @@ export function useStacksBroadcastSwap() { } catch (e) { setIsIdle(); navigate(RouteUrls.TransactionBroadcastError, { - state: { message: e instanceof Error ? e.message : 'Unknown error' }, + state: { message: isError(e) ? e.message : 'Unknown error' }, }); } finally { setIsIdle(); diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.layout.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.layout.tsx deleted file mode 100644 index 6f85aeafa2c..00000000000 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Stack } from 'leather-styles/jsx'; - -import { HasChildren } from '@app/common/has-children'; -import { Flag } from '@app/components/layout/flag'; - -export function SwapAssetItemLayout({ - children, - icon, - ...rest -}: HasChildren & { icon: React.JSX.Element }) { - return ( - - {children} - - ); -} diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx index 06b608c7194..f0f9e632948 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx +++ b/src/app/pages/swap/swap-choose-asset/components/swap-asset-item.tsx @@ -1,19 +1,20 @@ -import { HStack, styled } from 'leather-styles/jsx'; +import { SwapSelectors } from '@tests/selectors/swap.selectors'; +import { styled } from 'leather-styles/jsx'; import { formatMoneyWithoutSymbol } from '@app/common/money/format-money'; -import { usePressable } from '@app/components/item-hover'; import { useGetFungibleTokenMetadataQuery } from '@app/query/stacks/tokens/fungible-tokens/fungible-token-metadata.query'; import { isFtAsset } from '@app/query/stacks/tokens/token-metadata.utils'; +import { ItemInteractive } from '@app/ui/components/item/item-interactive'; +import { ItemLayout } from '@app/ui/components/item/item.layout'; import { useAlexSdkBalanceAsFiat } from '../../hooks/use-alex-sdk-fiat-price'; import { SwapAsset } from '../../hooks/use-swap-form'; -import { SwapAssetItemLayout } from './swap-asset-item.layout'; interface SwapAssetItemProps { asset: SwapAsset; + onClick(): void; } -export function SwapAssetItem({ asset }: SwapAssetItemProps) { - const [component, bind] = usePressable(true); +export function SwapAssetItem({ asset, onClick }: SwapAssetItemProps) { const balanceAsFiat = useAlexSdkBalanceAsFiat(asset.balance, asset.price); const { data: ftMetadata } = useGetFungibleTokenMetadataQuery(asset.principal); @@ -21,21 +22,14 @@ export function SwapAssetItem({ asset }: SwapAssetItemProps) { const displayName = asset.displayName ?? ftMetadataName; return ( - } - {...bind} - > - - {displayName} - {formatMoneyWithoutSymbol(asset.balance)} - - - {asset.name} - - {balanceAsFiat} - - - {component} - + + } + titleLeft={displayName} + captionLeft={asset.name} + titleRight={formatMoneyWithoutSymbol(asset.balance)} + captionRight={balanceAsFiat} + /> + ); } diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx index e7feee61700..eb40bfdcbf4 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx +++ b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.layout.tsx @@ -2,7 +2,7 @@ import { Stack, StackProps } from 'leather-styles/jsx'; export function SwapAssetListLayout({ children }: StackProps) { return ( - + {children} ); diff --git a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx index bc2875175ff..32c2028fd89 100644 --- a/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx +++ b/src/app/pages/swap/swap-choose-asset/components/swap-asset-list.tsx @@ -1,9 +1,7 @@ import { useNavigate } from 'react-router-dom'; -import { SwapSelectors } from '@tests/selectors/swap.selectors'; import BigNumber from 'bignumber.js'; import { useFormikContext } from 'formik'; -import { styled } from 'leather-styles/jsx'; import { createMoney } from '@shared/models/money.model'; import { isUndefined } from '@shared/utils'; @@ -68,15 +66,11 @@ export function SwapAssetList({ assets }: SwapAssetList) { return ( {selectableAssets.map(asset => ( - onChooseAsset(asset)} - textAlign="left" - type="button" - > - - + /> ))} ); diff --git a/src/app/pages/transaction-request/transaction-request.tsx b/src/app/pages/transaction-request/transaction-request.tsx index e565a736125..be8cd8cdb05 100644 --- a/src/app/pages/transaction-request/transaction-request.tsx +++ b/src/app/pages/transaction-request/transaction-request.tsx @@ -32,7 +32,7 @@ import { PostConditions } from '@app/features/stacks-transaction-request/post-co import { StxTransferDetails } from '@app/features/stacks-transaction-request/stx-transfer-details/stx-transfer-details'; import { SubmitAction } from '@app/features/stacks-transaction-request/submit-action'; import { TransactionError } from '@app/features/stacks-transaction-request/transaction-error/transaction-error'; -import { useCurrentStacksAccountAnchoredBalances } from '@app/query/stacks/balance/stx-balance.hooks'; +import { useCurrentStacksAccountBalances } from '@app/query/stacks/balance/stx-balance.hooks'; import { useCalculateStacksTxFees } from '@app/query/stacks/fees/fees.hooks'; import { useNextNonce } from '@app/query/stacks/nonce/account-nonces.hooks'; import { useTransactionRequestState } from '@app/store/transactions/requests.hooks'; @@ -52,7 +52,7 @@ function TransactionRequestBase() { const { data: stxFees } = useCalculateStacksTxFees(unsignedTx.transaction); const analytics = useAnalytics(); const generateUnsignedTx = useGenerateUnsignedStacksTransaction(); - const { data: stacksBalances } = useCurrentStacksAccountAnchoredBalances(); + const { data: stacksBalances } = useCurrentStacksAccountBalances(); const { data: nextNonce } = useNextNonce(); const navigate = useNavigate(); const signStacksTransaction = useSignStacksTransaction(); diff --git a/src/app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction.ts b/src/app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction.ts index 84065d865c5..298a6643721 100644 --- a/src/app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction.ts +++ b/src/app/query/bitcoin/transaction/use-bitcoin-broadcast-transaction.ts @@ -1,5 +1,7 @@ import { useCallback, useState } from 'react'; +import { isError } from '@shared/utils'; + import { useAnalytics } from '@app/common/hooks/analytics/use-analytics'; import { delay } from '@app/common/utils'; import { useBitcoinClient } from '@app/store/common/api-clients.hooks'; @@ -30,7 +32,10 @@ export function useBitcoinBroadcastTransaction() { return txid; } catch (e) { onError?.(e as Error); - void analytics.track('error_broadcasting_transaction', { error: e }); + void analytics.track('error_broadcasting_transaction', { + errorName: isError(e) ? e.name : 'unknown', + errorMsg: isError(e) ? e.message : 'unknown', + }); return; } finally { setIsBroadcasting(false); diff --git a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts index 6e81ec2a351..7458e315ee5 100644 --- a/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts +++ b/src/app/query/stacks/balance/stacks-ft-balances.hooks.ts @@ -14,47 +14,27 @@ import { isFtAsset } from '../tokens/token-metadata.utils'; import { addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance, convertFtBalancesToStacksFungibleTokenAssetBalanceType, - convertNftBalancesToStacksNonFungibleTokenAssetBalanceType, createStacksCryptoCurrencyAssetTypeWrapper, createStacksFtCryptoAssetBalanceTypeWrapper, } from './stacks-ft-balances.utils'; import { parseBalanceResponse } from './stx-balance.hooks'; -import { - useAnchoredStacksAccountBalanceQuery, - useUnanchoredStacksAccountBalanceQuery, -} from './stx-balance.query'; - -export function useStacksAnchoredCryptoCurrencyAssetBalance(address: string) { - return useAnchoredStacksAccountBalanceQuery(address, { - select: resp => - createStacksCryptoCurrencyAssetTypeWrapper(parseBalanceResponse(resp).stx.unlockedStx.amount), - }); -} +import { useStacksAccountBalanceQuery } from './stx-balance.query'; -// we will probably need this in the future -// ts-unused-exports:disable-next-line -export function useStacksUnanchoredCryptoCurrencyAssetBalance(address: string) { - return useUnanchoredStacksAccountBalanceQuery(address, { +export function useStacksCryptoCurrencyAssetBalance(address: string) { + return useStacksAccountBalanceQuery(address, { select: resp => createStacksCryptoCurrencyAssetTypeWrapper(parseBalanceResponse(resp).stx.unlockedStx.amount), }); } -function useStacksFungibleTokenAssetBalancesAnchored(address: string) { - return useAnchoredStacksAccountBalanceQuery(address, { - select: resp => convertFtBalancesToStacksFungibleTokenAssetBalanceType(resp.fungible_tokens), - }); -} - -function useStacksFungibleTokenAssetBalancesUnanchored(address: string) { - return useUnanchoredStacksAccountBalanceQuery(address, { +function useStacksFungibleTokenAssetBalances(address: string) { + return useStacksAccountBalanceQuery(address, { select: resp => convertFtBalancesToStacksFungibleTokenAssetBalanceType(resp.fungible_tokens), }); } -export function useStacksFungibleTokenAssetBalancesAnchoredWithMetadata(address: string) { - const { data: initializedAssetBalances = [] } = - useStacksFungibleTokenAssetBalancesAnchored(address); +export function useStacksFungibleTokenAssetBalancesWithMetadata(address: string) { + const { data: initializedAssetBalances = [] } = useStacksFungibleTokenAssetBalances(address); const ftAssetsMetadata = useGetFungibleTokenMetadataListQuery( initializedAssetBalances.map(assetBalance => @@ -77,38 +57,10 @@ export function useStacksFungibleTokenAssetBalancesAnchoredWithMetadata(address: ); } -function useStacksFungibleTokenAssetBalancesUnanchoredWithMetadata( - address: string -): StacksFungibleTokenAssetBalance[] { - const { data: initializedAssetBalances = [] } = - useStacksFungibleTokenAssetBalancesUnanchored(address); - const ftAssetsMetadata = useGetFungibleTokenMetadataListQuery( - initializedAssetBalances.map(assetBalance => - formatContractId(assetBalance.asset.contractAddress, assetBalance.asset.contractName) - ) - ); - - return useMemo( - () => - initializedAssetBalances.map((assetBalance, i) => { - const metadata = ftAssetsMetadata[i].data; - if (!(metadata && isFtAsset(metadata))) return assetBalance; - return addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( - assetBalance, - metadata - ); - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [initializedAssetBalances] - ); -} - export function useStacksFungibleTokenAssetBalance(contractId: string) { const account = useCurrentStacksAccount(); const navigate = useNavigate(); - const assetBalances = useStacksFungibleTokenAssetBalancesUnanchoredWithMetadata( - account?.address ?? '' - ); + const assetBalances = useStacksFungibleTokenAssetBalancesWithMetadata(account?.address ?? ''); return useMemo(() => { const balance = assetBalances.find(assetBalance => assetBalance.asset.contractId.includes(contractId) @@ -124,21 +76,9 @@ export function useStacksFungibleTokenAssetBalance(contractId: string) { export function useTransferableStacksFungibleTokenAssetBalances( address: string ): StacksFungibleTokenAssetBalance[] { - const assetBalances = useStacksFungibleTokenAssetBalancesUnanchoredWithMetadata(address); + const assetBalances = useStacksFungibleTokenAssetBalancesWithMetadata(address); return useMemo( () => assetBalances.filter(assetBalance => assetBalance.asset.canTransfer), [assetBalances] ); } - -// TODO: Remove? -// ts-unused-exports:disable-next-line -export function useStacksNonFungibleTokenAssetsUnanchored() { - const account = useCurrentStacksAccount(); - return useUnanchoredStacksAccountBalanceQuery(account?.address ?? '', { - select: resp => - convertNftBalancesToStacksNonFungibleTokenAssetBalanceType( - parseBalanceResponse(resp).non_fungible_tokens - ), - }); -} diff --git a/src/app/query/stacks/balance/stacks-ft-balances.utils.ts b/src/app/query/stacks/balance/stacks-ft-balances.utils.ts index ec183803008..b93f7bd0211 100644 --- a/src/app/query/stacks/balance/stacks-ft-balances.utils.ts +++ b/src/app/query/stacks/balance/stacks-ft-balances.utils.ts @@ -6,7 +6,6 @@ import type { AccountBalanceResponseBigNumber } from '@shared/models/account.mod import type { StacksCryptoCurrencyAssetBalance, StacksFungibleTokenAssetBalance, - StacksNonFungibleTokenAssetBalance, } from '@shared/models/crypto-asset-balance.model'; import { createMoney } from '@shared/models/money.model'; @@ -53,25 +52,6 @@ export function createStacksFtCryptoAssetBalanceTypeWrapper( }; } -function createStacksNftCryptoAssetBalanceTypeWrapper( - balance: BigNumber, - key: string -): StacksNonFungibleTokenAssetBalance { - const { address, contractName, assetName } = getAssetStringParts(key); - return { - blockchain: 'stacks', - type: 'non-fungible-token', - count: balance, - asset: { - contractAddress: address, - contractAssetName: assetName, - contractName, - imageCanonicalUri: '', - name: '', - }, - }; -} - export function convertFtBalancesToStacksFungibleTokenAssetBalanceType( ftBalances: AccountBalanceResponseBigNumber['fungible_tokens'] ) { @@ -86,17 +66,6 @@ export function convertFtBalancesToStacksFungibleTokenAssetBalanceType( ); } -export function convertNftBalancesToStacksNonFungibleTokenAssetBalanceType( - nftBalances: AccountBalanceResponseBigNumber['non_fungible_tokens'] -) { - return Object.entries(nftBalances) - .map(([key, value]) => { - const count = new BigNumber(value.count); - return createStacksNftCryptoAssetBalanceTypeWrapper(count, key); - }) - .filter(assetBalance => !assetBalance?.count.isEqualTo(0)); -} - export function addQueriedMetadataToInitializedStacksFungibleTokenAssetBalance( assetBalance: StacksFungibleTokenAssetBalance, metadata: FtMetadataResponse diff --git a/src/app/query/stacks/balance/stx-balance.hooks.ts b/src/app/query/stacks/balance/stx-balance.hooks.ts index e5076ddbc95..82736e0cb5c 100644 --- a/src/app/query/stacks/balance/stx-balance.hooks.ts +++ b/src/app/query/stacks/balance/stx-balance.hooks.ts @@ -10,7 +10,7 @@ import { Money, createMoney } from '@shared/models/money.model'; import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { accountBalanceStxKeys } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; -import { useAnchoredStacksAccountBalanceQuery } from './stx-balance.query'; +import { useStacksAccountBalanceQuery } from './stx-balance.query'; export function parseBalanceResponse(balances: AddressBalanceResponse) { const stxMoney = Object.fromEntries( @@ -30,13 +30,13 @@ export function parseBalanceResponse(balances: AddressBalanceResponse) { return { ...balances, stx }; } -export function useAnchoredStacksAccountBalances(address: string) { - return useAnchoredStacksAccountBalanceQuery(address, { +export function useStacksAccountBalances(address: string) { + return useStacksAccountBalanceQuery(address, { select: resp => parseBalanceResponse(resp), }); } -export function useCurrentStacksAccountAnchoredBalances() { +export function useCurrentStacksAccountBalances() { const account = useCurrentStacksAccount(); - return useAnchoredStacksAccountBalances(account?.address ?? ''); + return useStacksAccountBalances(account?.address ?? ''); } diff --git a/src/app/query/stacks/balance/stx-balance.query.ts b/src/app/query/stacks/balance/stx-balance.query.ts index e01331614b4..5cb0b95910b 100644 --- a/src/app/query/stacks/balance/stx-balance.query.ts +++ b/src/app/query/stacks/balance/stx-balance.query.ts @@ -4,10 +4,7 @@ import { AddressBalanceResponse } from '@shared/models/account.model'; import { AppUseQueryConfig } from '@app/query/query-config'; import { StacksClient } from '@app/query/stacks/stacks-client'; -import { - useStacksClientAnchored, - useStacksClientUnanchored, -} from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { RateLimiter, useHiroApiRateLimiter } from '../rate-limiter'; @@ -30,33 +27,17 @@ function fetchAccountBalance(client: StacksClient, limiter: RateLimiter) { type FetchAccountBalanceResp = Awaited>>; -export function useUnanchoredStacksAccountBalanceQuery( +export function useStacksAccountBalanceQuery( address: string, options?: AppUseQueryConfig ) { - const client = useStacksClientUnanchored(); - const limiter = useHiroApiRateLimiter(); - - return useQuery({ - enabled: !!address, - queryKey: ['get-address-stx-balance', address], - queryFn: () => fetchAccountBalance(client, limiter)(address), - ...balanceQueryOptions, - ...options, - }); -} - -export function useAnchoredStacksAccountBalanceQuery( - address: string, - options?: AppUseQueryConfig -) { - const client = useStacksClientAnchored(); + const client = useStacksClient(); const limiter = useHiroApiRateLimiter(); const network = useCurrentNetworkState(); return useQuery({ enabled: !!address, - queryKey: ['get-address-anchored-stx-balance', address, network.id], + queryKey: ['get-address-stx-balance', address, network.id], queryFn: () => fetchAccountBalance(client, limiter)(address), ...balanceQueryOptions, ...options, diff --git a/src/app/query/stacks/bns/bns.query.ts b/src/app/query/stacks/bns/bns.query.ts index fbab225cf1f..29603e09bfd 100644 --- a/src/app/query/stacks/bns/bns.query.ts +++ b/src/app/query/stacks/bns/bns.query.ts @@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'; import { AppUseQueryConfig } from '@app/query/query-config'; import { QueryPrefixes } from '@app/query/query-prefixes'; import { StacksClient } from '@app/query/stacks/stacks-client'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { RateLimiter, useHiroApiRateLimiter } from '../rate-limiter'; @@ -45,7 +45,7 @@ export function useGetBnsNamesOwnedByAddress ) { - const client = useStacksClientUnanchored(); + const client = useStacksClient(); const limiter = useHiroApiRateLimiter(); const { isTestnet } = useCurrentNetworkState(); return useQuery({ diff --git a/src/app/query/stacks/contract/contract.query.ts b/src/app/query/stacks/contract/contract.query.ts index 84a3508352c..7bcbd70d1a9 100644 --- a/src/app/query/stacks/contract/contract.query.ts +++ b/src/app/query/stacks/contract/contract.query.ts @@ -3,12 +3,12 @@ import { useQuery } from '@tanstack/react-query'; import { ContractInterfaceResponseWithFunctions } from '@shared/models/contract-types'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useHiroApiRateLimiter } from '../rate-limiter'; export function useGetContractInterface(transactionRequest: ContractCallPayload | null) { - const { smartContractsApi } = useStacksClientUnanchored(); + const { smartContractsApi } = useStacksClient(); const limiter = useHiroApiRateLimiter(); async function fetchContractInterface() { diff --git a/src/app/query/stacks/info/block-time.query.ts b/src/app/query/stacks/info/block-time.query.ts index 1eb8d1541cf..eb8064cbd3d 100644 --- a/src/app/query/stacks/info/block-time.query.ts +++ b/src/app/query/stacks/info/block-time.query.ts @@ -1,9 +1,9 @@ import { useQuery } from '@tanstack/react-query'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; export function useGetStackNetworkBlockTimeQuery() { - const client = useStacksClientUnanchored(); + const client = useStacksClient(); return useQuery({ queryKey: ['stacks-block-time'], diff --git a/src/app/query/stacks/mempool/mempool.hooks.ts b/src/app/query/stacks/mempool/mempool.hooks.ts index bdf48e172d7..1557b6ab0c6 100644 --- a/src/app/query/stacks/mempool/mempool.hooks.ts +++ b/src/app/query/stacks/mempool/mempool.hooks.ts @@ -19,7 +19,7 @@ import { useAccountMempoolQuery } from './mempool.query'; const droppedCache = new Map(); -function useAccountUnanchoredMempoolTransactions(address: string) { +function useAccountMempoolTransactions(address: string) { const analytics = useAnalytics(); const query = useAccountMempoolQuery(address); const accountMempoolTxs = query.data; @@ -51,7 +51,7 @@ function useAccountUnanchoredMempoolTransactions(address: string) { export function useStacksPendingTransactions() { const address = useCurrentAccountStxAddressState(); - const { query, transactions } = useAccountUnanchoredMempoolTransactions(address ?? ''); + const { query, transactions } = useAccountMempoolTransactions(address ?? ''); return useMemo(() => { const nonEmptyTransactions = transactions.filter(tx => !!tx) as MempoolTransaction[]; return { query, transactions: nonEmptyTransactions }; diff --git a/src/app/query/stacks/mempool/mempool.query.ts b/src/app/query/stacks/mempool/mempool.query.ts index 455371b3da5..bed57dfb823 100644 --- a/src/app/query/stacks/mempool/mempool.query.ts +++ b/src/app/query/stacks/mempool/mempool.query.ts @@ -1,16 +1,15 @@ import { MempoolTransaction } from '@stacks/stacks-blockchain-api-types'; import { useQuery } from '@tanstack/react-query'; -import { queryClient } from '@app/common/persistence'; import { safelyFormatHexTxid } from '@app/common/utils/safe-handle-txid'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useSubmittedTransactionsActions } from '@app/store/submitted-transactions/submitted-transactions.hooks'; import { useSubmittedTransactions } from '@app/store/submitted-transactions/submitted-transactions.selectors'; import { useHiroApiRateLimiter } from '../rate-limiter'; export function useAccountMempoolQuery(address: string) { - const client = useStacksClientUnanchored(); + const client = useStacksClient(); const submittedTransactions = useSubmittedTransactions(); const submittedTransactionsActions = useSubmittedTransactionsActions(); const limiter = useHiroApiRateLimiter(); @@ -25,8 +24,6 @@ export function useAccountMempoolQuery(address: string) { queryKey: ['account-mempool', address], queryFn: accountMempoolFetcher, onSuccess: data => { - void queryClient.invalidateQueries({ queryKey: ['account-microblock'] }); - const pendingTxids = (data.results as MempoolTransaction[]).map(tx => tx.tx_id); submittedTransactions.map(tx => { if (pendingTxids.includes(safelyFormatHexTxid(tx.txId))) diff --git a/src/app/query/stacks/microblock/microblock.query.ts b/src/app/query/stacks/microblock/microblock.query.ts deleted file mode 100644 index 2bb952527d7..00000000000 --- a/src/app/query/stacks/microblock/microblock.query.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TokenTransferTransaction } from '@stacks/stacks-blockchain-api-types'; -import { useQuery } from '@tanstack/react-query'; - -import { createMoney } from '@shared/models/money.model'; - -import { sumNumbers } from '@app/common/math/helpers'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; - -export function useCurrentAccountMicroblockBalanceQuery(address: string) { - const client = useStacksClientUnanchored(); - - async function accountMicroblockFetcher() { - const txs = await client.microblocksApi.getUnanchoredTxs({}); - return txs.results as TokenTransferTransaction[]; - } - return useQuery({ - enabled: !!address, - queryKey: ['account-microblock', address], - queryFn: accountMicroblockFetcher, - select: resp => { - const senderMicroblockTxs = resp.filter(tx => tx.sender_address === address); - return createMoney( - sumNumbers(senderMicroblockTxs.map(tx => Number(tx.token_transfer.amount))), - 'STX' - ); - }, - refetchOnWindowFocus: false, - }); -} diff --git a/src/app/query/stacks/nonce/account-nonces.query.ts b/src/app/query/stacks/nonce/account-nonces.query.ts index e4d29d913c2..e3059a52b39 100644 --- a/src/app/query/stacks/nonce/account-nonces.query.ts +++ b/src/app/query/stacks/nonce/account-nonces.query.ts @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query'; import { AppUseQueryConfig } from '@app/query/query-config'; import { useCurrentAccountStxAddressState } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { useCurrentNetworkState } from '@app/store/networks/networks.hooks'; import { RateLimiter, useHiroApiRateLimiter } from '../rate-limiter'; @@ -31,7 +31,7 @@ export function useGetAccountNoncesQuery { - const url = new URL(context.url); - if (!url.toString().includes('/v2')) url.searchParams.set('unanchored', 'true'); - return Promise.resolve({ - init: context.init, - url: url.toString(), - }); - }, -}; - -export function createStacksUnanchoredConfig(basePath: string) { - const middleware: Middleware[] = []; - if (MICROBLOCKS_ENABLED) middleware.push(unanchoredStacksMiddleware); - return new Configuration({ - basePath, - fetchApi, - middleware, - }); -} - -export function createStacksAnchoredConfig(basePath: string) { +export function createStacksClientConfig(basePath: string) { return new Configuration({ basePath, fetchApi, diff --git a/src/app/store/common/api-clients.hooks.ts b/src/app/store/common/api-clients.hooks.ts index 90482ad8885..383095baea8 100644 --- a/src/app/store/common/api-clients.hooks.ts +++ b/src/app/store/common/api-clients.hooks.ts @@ -8,11 +8,7 @@ import { whenStacksChainId } from '@app/common/utils'; import { BitcoinClient } from '@app/query/bitcoin/bitcoin-client'; import { StacksClient } from '@app/query/stacks/stacks-client'; import { TokenMetadataClient } from '@app/query/stacks/token-metadata-client'; -import { - createStacksAnchoredConfig, - createStacksUnanchoredConfig, - createTokenMetadataConfig, -} from '@app/query/stacks/utils'; +import { createStacksClientConfig, createTokenMetadataConfig } from '@app/query/stacks/utils'; import { useCurrentNetworkState } from '../networks/networks.hooks'; @@ -21,22 +17,11 @@ export function useBitcoinClient() { return new BitcoinClient(network.chain.bitcoin.bitcoinUrl); } -// Unanchored by default (microblocks) -export function useStacksClientUnanchored() { +export function useStacksClient() { const network = useCurrentNetworkState(); return useMemo(() => { - const config = createStacksUnanchoredConfig(network.chain.stacks.url); - return new StacksClient(config); - }, [network.chain.stacks.url]); -} - -// Anchored (NON-microblocks) -export function useStacksClientAnchored() { - const network = useCurrentNetworkState(); - - return useMemo(() => { - const config = createStacksAnchoredConfig(network.chain.stacks.url); + const config = createStacksClientConfig(network.chain.stacks.url); return new StacksClient(config); }, [network.chain.stacks.url]); } diff --git a/src/app/store/transactions/fees.hooks.ts b/src/app/store/transactions/fees.hooks.ts deleted file mode 100644 index 23359844bca..00000000000 --- a/src/app/store/transactions/fees.hooks.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { StacksTransaction } from '@stacks/transactions'; - -import { logger } from '@shared/logger'; -import { RouteUrls } from '@shared/route-urls'; - -import { LoadingKeys } from '@app/common/hooks/use-loading'; -import { useSubmitTransactionCallback } from '@app/common/hooks/use-submit-stx-transaction'; -import { useRawTxIdState } from '@app/store/transactions/raw.hooks'; - -import { useSignStacksTransaction } from './transaction.hooks'; - -export const useReplaceByFeeSoftwareWalletSubmitCallBack = () => { - const [, setTxId] = useRawTxIdState(); - const signTx = useSignStacksTransaction(); - const navigate = useNavigate(); - - const submitTransaction = useSubmitTransactionCallback({ - loadingKey: LoadingKeys.INCREASE_FEE_DRAWER, - }); - - return useCallback( - async (rawTx: StacksTransaction) => { - if (!rawTx) return; - const signedTx = await signTx(rawTx); - if (!signedTx) { - logger.warn('Error signing transaction when replacing by fee'); - return; - } - await submitTransaction({ - onSuccess() { - setTxId(null); - navigate(RouteUrls.IncreaseFeeSent); - }, - onError() { - logger.error('Error submitting transaction'); - }, - replaceByFee: true, - })(signedTx); - }, - [navigate, setTxId, signTx, submitTransaction] - ); -}; diff --git a/src/app/store/transactions/raw.hooks.ts b/src/app/store/transactions/raw.hooks.ts index 30e099e304f..eaa5efa80d6 100644 --- a/src/app/store/transactions/raw.hooks.ts +++ b/src/app/store/transactions/raw.hooks.ts @@ -4,7 +4,7 @@ import { useAsync } from 'react-async-hook'; import { deserializeTransaction } from '@stacks/transactions'; import { useAtom } from 'jotai'; -import { useStacksClientUnanchored } from '@app/store/common/api-clients.hooks'; +import { useStacksClient } from '@app/store/common/api-clients.hooks'; import { rawTxIdState } from '@app/store/transactions/raw'; export function useRawTxIdState() { @@ -15,7 +15,7 @@ const rawTxCache = new Map(); function useRawTxState() { const [txId] = useRawTxIdState(); - const { transactionsApi } = useStacksClientUnanchored(); + const { transactionsApi } = useStacksClient(); return useAsync(async () => { if (!txId) return; diff --git a/src/app/ui/components/button/button.stories.tsx b/src/app/ui/components/button/button.stories.tsx index c0d2277c292..796d2a6a9a0 100644 --- a/src/app/ui/components/button/button.stories.tsx +++ b/src/app/ui/components/button/button.stories.tsx @@ -26,6 +26,18 @@ export const Button: Story = { }, }; +export const Disabled: Story = { + parameters: { + controls: { include: ['size', 'variant'] }, + }, + args: { + disabled: true, + children: 'Button', + size: 'md', + variant: 'solid', + }, +}; + // TODO: Remove invert code export const InvertSolid: Story = { parameters: { diff --git a/src/app/ui/components/dowpdown-menu/dropdown-menu.tsx b/src/app/ui/components/dowpdown-menu/dropdown-menu.tsx index 169d56ed1c7..38d0bbf67ea 100644 --- a/src/app/ui/components/dowpdown-menu/dropdown-menu.tsx +++ b/src/app/ui/components/dowpdown-menu/dropdown-menu.tsx @@ -3,6 +3,8 @@ import { ReactNode, forwardRef } from 'react'; import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; import { css } from 'leather-styles/css'; +import { itemBaseStyles, itemInteractiveStyles } from '../item/item-interactive'; + export interface DropdownMenuItem { iconLeft?: ReactNode; iconRight?: ReactNode; @@ -67,20 +69,12 @@ const Label: typeof RadixDropdownMenu.Label = forwardRef((props, ref) => ( )); -const dropdownMenuItemStyles = css({ - bg: 'accent.background-primary', - color: 'accent.text-primary', - height: 'auto', - outline: 'none', - userSelect: 'none', - p: 'space.03', - - '&[data-highlighted]': { - bg: 'accent.component-background-hover', - }, -}); const Item: typeof RadixDropdownMenu.Item = forwardRef((props, ref) => ( - + )); const dropdownMenuSeparatorStyles = css({ diff --git a/src/app/ui/components/dynamic-color-circle.tsx b/src/app/ui/components/dynamic-color-circle.tsx index e0d62e71db4..2b4d0f0e495 100644 --- a/src/app/ui/components/dynamic-color-circle.tsx +++ b/src/app/ui/components/dynamic-color-circle.tsx @@ -6,7 +6,7 @@ interface DynamicColorCircleProps extends CircleProps { } export function DynamicColorCircle({ children, - sizeParam = '36', + sizeParam = '40', value, ...props }: DynamicColorCircleProps) { diff --git a/src/app/ui/components/flag/flag.stories.tsx b/src/app/ui/components/flag/flag.stories.tsx new file mode 100644 index 00000000000..b1f80906d52 --- /dev/null +++ b/src/app/ui/components/flag/flag.stories.tsx @@ -0,0 +1,35 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { Box, Circle } from 'leather-styles/jsx'; + +import { Flag as Component } from './flag'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Layout/Flag', + argTypes: { + align: { + options: ['top', 'middle', 'bottom'], + control: { type: 'radio' }, + defaultValue: 'middle', + }, + }, + parameters: { + controls: { include: ['align'] }, + }, + render: ({ children, ...args }) => ( + }> + {children} + + ), +}; + +export default meta; + +type Story = StoryObj; + +export const Flag: Story = { + args: { + children: , + }, +}; diff --git a/src/app/components/layout/flag.tsx b/src/app/ui/components/flag/flag.tsx similarity index 53% rename from src/app/components/layout/flag.tsx rename to src/app/ui/components/flag/flag.tsx index 8a7fe90663a..e4a284f24e4 100644 --- a/src/app/components/layout/flag.tsx +++ b/src/app/ui/components/flag/flag.tsx @@ -1,19 +1,24 @@ +import { css } from 'leather-styles/css'; import { Box, Flex, FlexProps } from 'leather-styles/jsx'; import { SpacingToken } from 'leather-styles/tokens'; -function alignToFlexProp(alignment: FlagAlignment) { - return { - top: 'start', - middle: 'center', - bottom: 'end', - }[alignment]; -} +const flagStyles = css({ + '&[data-align="top"]': { + alignItems: 'start', + }, + '&[data-align="middle"]': { + alignItems: 'center', + }, + '&[data-align="bottom"]': { + alignItems: 'end', + }, +}); type FlagAlignment = 'top' | 'middle' | 'bottom'; interface FlagProps extends FlexProps { spacing?: SpacingToken; - align?: 'top' | 'middle' | 'bottom'; + align?: FlagAlignment; children: React.ReactNode; img: React.ReactNode; } @@ -25,9 +30,15 @@ interface FlagProps extends FlexProps { * 1st. Image content * 2nd. Body content */ -export function Flag({ spacing = 'space.02', align = 'top', img, children, ...props }: FlagProps) { +export function Flag({ + spacing = 'space.03', + align = 'middle', + img, + children, + ...props +}: FlagProps) { return ( - + {img} {children} diff --git a/src/app/ui/components/item/item-interactive.stories.tsx b/src/app/ui/components/item/item-interactive.stories.tsx new file mode 100644 index 00000000000..6bcc9274e00 --- /dev/null +++ b/src/app/ui/components/item/item-interactive.stories.tsx @@ -0,0 +1,72 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { Button } from '../button/button'; +import { BtcIcon } from '../icons/btc-icon'; +import { CopyIcon } from '../icons/copy-icon'; +import { QrCodeIcon } from '../icons/qr-code-icon'; +import { ItemInteractive as Component } from './item-interactive'; +import { ItemWithButtonsLayout } from './item-with-buttons.layout'; +import { ItemLayout } from './item.layout'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Item Interactive', + parameters: { + controls: { include: [] }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const ItemInteractive: Story = { + args: { + onClick: () => {}, + children: ( + } + titleLeft="Label" + captionLeft="Caption" + titleRight="1,000.00 ABC" + captionRight="$1,000.00" + /> + ), + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + onClick: () => {}, + children: ( + } + titleLeft="Label" + captionLeft="Caption" + titleRight="1,000.00 ABC" + captionRight="$1,000.00" + /> + ), + }, +}; + +export const WithButtons: Story = { + args: { + children: ( + } + title="Label" + caption="Caption" + buttons={ + <> + + + + } + /> + ), + }, +}; diff --git a/src/app/ui/components/item/item-interactive.tsx b/src/app/ui/components/item/item-interactive.tsx new file mode 100644 index 00000000000..9a4667b681d --- /dev/null +++ b/src/app/ui/components/item/item-interactive.tsx @@ -0,0 +1,100 @@ +import { forwardRef } from 'react'; + +import { type RecipeVariantProps, css, cva } from 'leather-styles/css'; +import { Box, BoxProps } from 'leather-styles/jsx'; + +import { isDefined } from '@shared/utils'; + +const basePseudoOutlineProps = { + content: '""', + rounded: 'xs', + position: 'absolute', + top: 0, + left: 0, + bottom: 0, + right: 0, +}; + +const focusVisibleStyles = { + _before: { + ...basePseudoOutlineProps, + border: '2px solid', + borderColor: 'lightModeBlue.500', + }, + _focusWithin: { outline: 'none' }, +}; + +export const itemBaseStyles = css.raw({ + bg: 'accent.background-primary', + color: 'accent.text-primary', + cursor: 'default', + display: 'flex', + height: 'auto', + outline: 'none', + p: 'space.03', + position: 'relative', + rounded: 'xs', + userSelect: 'none', + width: '100%', +}); + +export const itemInteractiveStyles = css.raw({ + cursor: 'pointer', + + '&:is(:active)': { + bg: 'accent.component-background-pressed', + }, + '&:is(:focus-visible)': { + ...focusVisibleStyles, + }, + '&:is(:disabled, [data-disabled])': { + _active: { bg: 'unset' }, + _focus: { border: 'unset' }, + _hover: { bg: 'unset' }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, + '&:is(:hover, [data-highlighted])': { + _before: { borderColor: 'transparent' }, + bg: 'accent.component-background-hover', + }, +}); + +const itemRecipe = cva({ + base: itemBaseStyles, + variants: { + disabled: { true: {} }, + interactive: { + true: itemInteractiveStyles, + }, + }, +}); + +export const itemCaptionStyles = css({ + _groupDisabled: { color: 'accent.non-interactive' }, + color: 'accent.text-subdued', +}); + +export const itemChevronStyles = css({ + _groupDisabled: { color: 'accent.non-interactive' }, + color: 'accent.action-primary-default', +}); + +type ItemVariantProps = RecipeVariantProps; + +export const ItemInteractive = forwardRef( + (props, ref) => { + const { disabled, onClick, ...rest } = props; + const isInteractive = isDefined(onClick); + return ( + + ); + } +); diff --git a/src/app/ui/components/item/item-with-buttons.layout.tsx b/src/app/ui/components/item/item-with-buttons.layout.tsx new file mode 100644 index 00000000000..af00349f3f8 --- /dev/null +++ b/src/app/ui/components/item/item-with-buttons.layout.tsx @@ -0,0 +1,44 @@ +import { ReactNode } from 'react'; + +import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; + +import { Flag } from '../flag/flag'; +import { itemCaptionStyles } from './item-interactive'; + +interface ItemWithButtonsLayoutProps { + buttons: ReactNode; + caption: string; + flagImg: ReactNode; + title: string; +} +export function ItemWithButtonsLayout({ + buttons, + caption, + flagImg, + title, +}: ItemWithButtonsLayoutProps) { + return ( + + + + + {title} + + + {caption} + + + + {buttons} + + + + ); +} diff --git a/src/app/ui/components/item/item.layout.tsx b/src/app/ui/components/item/item.layout.tsx new file mode 100644 index 00000000000..da1dae0d8e2 --- /dev/null +++ b/src/app/ui/components/item/item.layout.tsx @@ -0,0 +1,80 @@ +import { ReactNode, isValidElement } from 'react'; + +import { Flex, HStack, Stack, styled } from 'leather-styles/jsx'; + +import { Flag } from '../flag/flag'; +import { CheckmarkIcon } from '../icons/checkmark-icon'; +import { ChevronUpIcon } from '../icons/chevron-up-icon'; +import { itemCaptionStyles, itemChevronStyles } from './item-interactive'; + +interface ItemLayoutProps { + captionLeft: ReactNode; + captionRight?: ReactNode; + flagImg: ReactNode; + isDisabled?: boolean; + isSelected?: boolean; + showChevron?: boolean; + titleLeft: ReactNode; + titleRight: ReactNode; +} +export function ItemLayout({ + captionLeft, + captionRight, + flagImg, + isSelected, + showChevron, + titleLeft, + titleRight, +}: ItemLayoutProps) { + return ( + + + + + {isValidElement(titleLeft) ? ( + titleLeft + ) : ( + + {titleLeft} + + )} + {isSelected && } + + {isValidElement(captionLeft) ? ( + captionLeft + ) : ( + + {captionLeft} + + )} + + + + {isValidElement(titleRight) ? ( + titleRight + ) : ( + + {titleRight} + + )} + {isValidElement(captionRight) ? ( + captionRight + ) : ( + + {captionRight} + + )} + + {showChevron && } + + + + ); +} diff --git a/src/app/ui/components/item/item.stories.tsx b/src/app/ui/components/item/item.stories.tsx new file mode 100644 index 00000000000..7a118342b18 --- /dev/null +++ b/src/app/ui/components/item/item.stories.tsx @@ -0,0 +1,25 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { Box, Circle } from 'leather-styles/jsx'; + +import { ItemLayout as Component } from './item.layout'; + +const meta: Meta = { + component: Component, + tags: ['autodocs'], + title: 'Layout/Item', +}; + +export default meta; +type Story = StoryObj; + +export const Item: Story = { + render: () => ( + } + titleLeft={} + captionLeft={} + titleRight={} + captionRight={} + /> + ), +}; diff --git a/src/app/ui/components/link/link.stories.tsx b/src/app/ui/components/link/link.stories.tsx index b92fd85c7b8..8048b8a5091 100644 --- a/src/app/ui/components/link/link.stories.tsx +++ b/src/app/ui/components/link/link.stories.tsx @@ -22,6 +22,18 @@ export const Link: Story = { }, }; +export const Disabled: Story = { + parameters: { + controls: { include: ['size', 'variant'] }, + }, + args: { + children: 'Link', + disabled: true, + size: 'md', + variant: 'underlined', + }, +}; + // TODO: Remove invert code export const InvertLink: Story = { parameters: { diff --git a/src/app/ui/components/link/link.tsx b/src/app/ui/components/link/link.tsx index ff714540708..6890f139d90 100644 --- a/src/app/ui/components/link/link.tsx +++ b/src/app/ui/components/link/link.tsx @@ -9,12 +9,12 @@ type LinkProps = Omit, keyof LinkVariant LinkVariantProps; export const Link = forwardRef((props: LinkProps, ref: ForwardedRef) => { - const { children, fullWidth, invert, size, variant, ...rest } = props; + const { children, disabled, fullWidth, invert, size, variant, ...rest } = props; return ( diff --git a/src/app/ui/components/select/select.tsx b/src/app/ui/components/select/select.tsx index 9953bdce076..166fd0b70e0 100644 --- a/src/app/ui/components/select/select.tsx +++ b/src/app/ui/components/select/select.tsx @@ -3,6 +3,8 @@ import { ReactNode, forwardRef } from 'react'; import * as RadixSelect from '@radix-ui/react-select'; import { css } from 'leather-styles/css'; +import { itemBaseStyles, itemInteractiveStyles } from '../item/item-interactive'; + export interface SelectItem { iconLeft?: ReactNode; iconRight?: ReactNode; @@ -79,20 +81,8 @@ const Label: typeof RadixSelect.Label = forwardRef((props, ref) => ( )); -const selectItemStyles = css({ - bg: 'accent.background-primary', - color: 'accent.text-primary', - height: 'auto', - outline: 'none', - userSelect: 'none', - p: 'space.03', - - '&[data-highlighted]': { - bg: 'accent.component-background-hover', - }, -}); const Item: typeof RadixSelect.Item = forwardRef((props, ref) => ( - + )); const ItemText = RadixSelect.ItemText; diff --git a/src/app/ui/components/tooltip/basic-tooltip.tsx b/src/app/ui/components/tooltip/basic-tooltip.tsx index e10e2d61616..56fcead792f 100644 --- a/src/app/ui/components/tooltip/basic-tooltip.tsx +++ b/src/app/ui/components/tooltip/basic-tooltip.tsx @@ -11,7 +11,6 @@ interface BasicTooltipProps { side?: RadixTooltip.TooltipContentProps['side']; asChild?: boolean; } - export function BasicTooltip({ children, label, disabled, side, asChild }: BasicTooltipProps) { const isDisabled = !label || disabled; return ( diff --git a/src/app/ui/components/typography/caption.tsx b/src/app/ui/components/typography/caption.tsx index 96eb6c55b1b..d87df4d218e 100644 --- a/src/app/ui/components/typography/caption.tsx +++ b/src/app/ui/components/typography/caption.tsx @@ -1,9 +1,17 @@ import { forwardRef } from 'react'; -import { BoxProps, styled } from 'leather-styles/jsx'; +import { HTMLStyledProps, styled } from 'leather-styles/jsx'; -export const Caption = forwardRef(({ children, ...props }, ref) => ( - - {children} - -)); +export const Caption = forwardRef>( + ({ children, ...props }, ref) => ( + + {children} + + ) +); diff --git a/src/app/ui/components/typography/title.tsx b/src/app/ui/components/typography/title.tsx index 0129f184d97..c8eaac73eec 100644 --- a/src/app/ui/components/typography/title.tsx +++ b/src/app/ui/components/typography/title.tsx @@ -1,15 +1,18 @@ import { forwardRef } from 'react'; -import { BoxProps, styled } from 'leather-styles/jsx'; +import { HTMLStyledProps, styled } from 'leather-styles/jsx'; -export const Title = forwardRef(({ children, ...props }, ref) => ( - - {children} - -)); +export const Title = forwardRef>( + ({ children, ...props }, ref) => ( + + {children} + + ) +); diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 696df810ed6..f3138a0868c 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,6 +1,5 @@ import { ChainID } from '@stacks/transactions'; -import { IS_TEST_ENV } from './environment'; import { Blockchains } from './models/blockchain.model'; export const gaiaUrl = 'https://hub.blockstack.org'; @@ -24,8 +23,6 @@ export const BTC_P2WPKH_DUST_AMOUNT = 294; export const KEBAB_REGEX = /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g; -export const MICROBLOCKS_ENABLED = !IS_TEST_ENV && true; - export const GITHUB_ORG = 'leather-wallet'; export const GITHUB_REPO = 'extension'; diff --git a/src/shared/models/crypto-asset-balance.model.ts b/src/shared/models/crypto-asset-balance.model.ts index ffcc63065b3..e4274cbeb7c 100644 --- a/src/shared/models/crypto-asset-balance.model.ts +++ b/src/shared/models/crypto-asset-balance.model.ts @@ -29,6 +29,7 @@ export interface StacksFungibleTokenAssetBalance { readonly balance: Money; } +// ts-unused-exports:disable-next-line export interface StacksNonFungibleTokenAssetBalance { readonly blockchain: 'stacks'; readonly type: 'non-fungible-token'; diff --git a/src/shared/models/transactions/stacks-transaction.model.ts b/src/shared/models/transactions/stacks-transaction.model.ts index fcc82ac9402..e9f19cabc82 100644 --- a/src/shared/models/transactions/stacks-transaction.model.ts +++ b/src/shared/models/transactions/stacks-transaction.model.ts @@ -1,7 +1,7 @@ import type { MempoolTransaction, Transaction } from '@stacks/stacks-blockchain-api-types'; export type StacksTx = MempoolTransaction | Transaction; -export type StacksTxStatus = 'failed' | 'pending' | 'success_anchor_block' | 'success_microblock'; +export type StacksTxStatus = 'failed' | 'pending' | 'success'; export interface StxTransfer { amount: string; diff --git a/src/shared/utils.ts b/src/shared/utils.ts index 34e35814db7..9605679b59e 100644 --- a/src/shared/utils.ts +++ b/src/shared/utils.ts @@ -29,6 +29,10 @@ export function isObject(value: unknown): value is object { return typeof value === 'object'; } +export function isError(value: unknown): value is Error { + return value instanceof Error; +} + export function isEmpty(value: object) { return Object.keys(value).length === 0; } diff --git a/theme/recipes/button-recipe.ts b/theme/recipes/button-recipe.ts index 5e26e867360..36da0ac019b 100644 --- a/theme/recipes/button-recipe.ts +++ b/theme/recipes/button-recipe.ts @@ -30,9 +30,6 @@ export const buttonRecipe = defineRecipe({ className: 'button', jsx: ['Button'], base: { - _disabled: { - cursor: 'not-allowed', - }, position: 'relative', rounded: 'xs', textStyle: 'label.02', @@ -54,8 +51,10 @@ export const buttonRecipe = defineRecipe({ bg: 'accent.action-primary-default', }, _disabled: { + _hover: { bg: 'accent.background-secondary' }, bg: 'accent.background-secondary', color: 'accent.non-interactive', + cursor: 'not-allowed', }, _focus: { _before: { @@ -74,6 +73,12 @@ export const buttonRecipe = defineRecipe({ _active: { bg: 'accent.component-background-pressed', }, + _disabled: { + _hover: { bg: 'unset' }, + border: '1px solid {colors.accent.non-interactive}', + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, _focus: { _before: { border: '3px solid {colors.focus}', @@ -90,6 +95,11 @@ export const buttonRecipe = defineRecipe({ _active: { bg: 'accent.component-background-pressed', }, + _disabled: { + _hover: { bg: 'unset' }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, _focus: { _before: { border: '3px solid {focus}', diff --git a/theme/recipes/link-recipe.ts b/theme/recipes/link-recipe.ts index 88ba089d678..f64aba9716a 100644 --- a/theme/recipes/link-recipe.ts +++ b/theme/recipes/link-recipe.ts @@ -6,11 +6,7 @@ export const linkRecipe = defineRecipe({ className: 'link', jsx: ['Link'], base: { - _disabled: { - cursor: 'not-allowed', - }, appearance: 'none', - color: 'accent.text-primary', display: 'inline', mb: 'space.01', p: 'unset', @@ -48,12 +44,6 @@ export const linkRecipe = defineRecipe({ }, color: 'accent.text-primary', }, - _disabled: { - _before: { - background: 'accent.non-interactive', - }, - color: 'accent.non-interactive', - }, _focus: { _before: { background: 'focus' }, color: 'accent.text-primary', @@ -64,6 +54,7 @@ export const linkRecipe = defineRecipe({ background: 'accent.action-primary-hover', }, }, + color: 'accent.text-primary', }, text: { @@ -84,13 +75,6 @@ export const linkRecipe = defineRecipe({ color: 'accent.text-primary', visibility: 'visible', }, - _disabled: { - _before: { - background: 'accent.non-interactive', - visibility: 'visible', - }, - color: 'accent.non-interactive', - }, _focus: { _before: { background: 'focus', @@ -105,12 +89,13 @@ export const linkRecipe = defineRecipe({ visibility: 'visible', }, }, + color: 'accent.text-primary', }, }, // TODO: Remove invert code invert: { true: {} }, - + disabled: { true: {} }, fullWidth: { true: { width: '100%' } }, }, @@ -122,8 +107,6 @@ export const linkRecipe = defineRecipe({ // TODO: Remove invert code compoundVariants: [ { - variant: 'underlined', - invert: true, css: { _focus: { _before: { @@ -140,6 +123,43 @@ export const linkRecipe = defineRecipe({ }, color: 'accent.background-secondary', }, + invert: true, + variant: 'underlined', + }, + { + css: { + _before: { + content: '""', + background: 'accent.non-interactive', + bottom: '-2px', + height: '2px', + left: 0, + position: 'absolute', + right: 0, + }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, + disabled: true, + variant: 'underlined', + }, + { + css: { + _before: { + content: '""', + background: 'accent.non-interactive', + bottom: '-2px', + height: '2px', + left: 0, + position: 'absolute', + right: 0, + visibility: 'visible', + }, + color: 'accent.non-interactive', + cursor: 'not-allowed', + }, + disabled: true, + variant: 'text', }, ], }); diff --git a/theme/semantic-tokens.ts b/theme/semantic-tokens.ts index 86757928986..cc2dd434cd9 100644 --- a/theme/semantic-tokens.ts +++ b/theme/semantic-tokens.ts @@ -4,8 +4,4 @@ import { defineSemanticTokens } from '@pandacss/dev'; // ts-unused-exports:disable-next-line export const semanticTokens = defineSemanticTokens({ ...leatherSemanticTokens, - // TODO: Move to mono repo - focus: { - value: { base: '{colors.lightModeBlue.500}', _dark: '{colors.darkModeBlue.500}' }, - }, }); diff --git a/yarn.lock b/yarn.lock index 73ab754db58..55279149757 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2484,10 +2484,10 @@ "@trivago/prettier-plugin-sort-imports" "^4.2.0" prettier "^3.0.3" -"@leather-wallet/tokens@0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@leather-wallet/tokens/-/tokens-0.0.5.tgz#075daf05a3d65451731a26f870058ec41b6b42b9" - integrity sha512-ljs4kSFEkTWtRa1+fEB1+mBQJgqYmoF1o/zbNOP23pjUCCgqLxTViOwZcFStqnTgf0hfu7JenGSsS/GV7HI21g== +"@leather-wallet/tokens@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@leather-wallet/tokens/-/tokens-0.0.6.tgz#9a26a30d431f3074cb7ea01e27c961bc29c1676b" + integrity sha512-77xEFIsucUoz8NPB7NVCuMi2wZpU5guNyfTUSlaCgKn6E1+9Vz2Jk1VEw+qXK1xuesDD7byCRuID6lohDHyL8A== "@ledgerhq/devices@^8.0.7", "@ledgerhq/devices@^8.2.0": version "8.2.0" @@ -5642,15 +5642,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/builder-manager@7.6.10": - version "7.6.10" - resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.10.tgz#fc30b19dd74e6f6ae896d8f4045552c3206c25f9" - integrity sha512-f+YrjZwohGzvfDtH8BHzqM3xW0p4vjjg9u7uzRorqUiNIAAKHpfNrZ/WvwPlPYmrpAHt4xX/nXRJae4rFSygPw== +"@storybook/builder-manager@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/builder-manager/-/builder-manager-7.6.12.tgz#6dd6ed1e0b440d7dd26dc5438e5e765aa464212e" + integrity sha512-AJFrtBj0R11OFwwz+2j+ivRzttWXT6LesSGoLnxown24EV9uLQoHtGb7GOA2GyzY5wjUJS9gQBPGHXjvQEfLJA== dependencies: "@fal-works/esbuild-plugin-global-externals" "^2.1.2" - "@storybook/core-common" "7.6.10" - "@storybook/manager" "7.6.10" - "@storybook/node-logger" "7.6.10" + "@storybook/core-common" "7.6.12" + "@storybook/manager" "7.6.12" + "@storybook/node-logger" "7.6.12" "@types/ejs" "^3.1.1" "@types/find-cache-dir" "^3.2.1" "@yarnpkg/esbuild-plugin-pnp" "^3.0.0-rc.10" @@ -5720,6 +5720,18 @@ telejson "^7.2.0" tiny-invariant "^1.3.1" +"@storybook/channels@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.12.tgz#eafcbb1c26de94ed15db62dd0f8ea88d20154312" + integrity sha512-TaPl5Y3lOoVi5kTLgKNRX8xh2sUPekH0Id1l4Ymw+lpgriEY6r60bmkZLysLG1GhlskpQ/da2+S2ap2ht8P2TQ== + dependencies: + "@storybook/client-logger" "7.6.12" + "@storybook/core-events" "7.6.12" + "@storybook/global" "^5.0.0" + qs "^6.10.0" + telejson "^7.2.0" + tiny-invariant "^1.3.1" + "@storybook/channels@7.6.7": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-7.6.7.tgz#23a0c59ebfdfbb83e4a49d8d3fafdd25a9a67140" @@ -5732,23 +5744,23 @@ telejson "^7.2.0" tiny-invariant "^1.3.1" -"@storybook/cli@7.6.10": - version "7.6.10" - resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.10.tgz#2436276c5404b166a9f795fef44bbd75826d9bfe" - integrity sha512-pK1MEseMm73OMO2OVoSz79QWX8ymxgIGM8IeZTCo9gImiVRChMNDFYcv8yPWkjuyesY8c15CoO48aR7pdA1OjQ== +"@storybook/cli@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/cli/-/cli-7.6.12.tgz#f114dc71799eec60cf92c1462c2418ae11711246" + integrity sha512-x4sG1oIVERxp+WnWUexVlgaJCFmML0kGi7a5qfx7z4vHMxCV/WG7g1q7mPS/kqStCGEiQdTciCqOEFqlMh9MLw== dependencies: "@babel/core" "^7.23.2" "@babel/preset-env" "^7.23.2" "@babel/types" "^7.23.0" "@ndelangen/get-tarball" "^3.0.7" - "@storybook/codemod" "7.6.10" - "@storybook/core-common" "7.6.10" - "@storybook/core-events" "7.6.10" - "@storybook/core-server" "7.6.10" - "@storybook/csf-tools" "7.6.10" - "@storybook/node-logger" "7.6.10" - "@storybook/telemetry" "7.6.10" - "@storybook/types" "7.6.10" + "@storybook/codemod" "7.6.12" + "@storybook/core-common" "7.6.12" + "@storybook/core-events" "7.6.12" + "@storybook/core-server" "7.6.12" + "@storybook/csf-tools" "7.6.12" + "@storybook/node-logger" "7.6.12" + "@storybook/telemetry" "7.6.12" + "@storybook/types" "7.6.12" "@types/semver" "^7.3.4" "@yarnpkg/fslib" "2.10.3" "@yarnpkg/libzip" "2.3.0" @@ -5785,6 +5797,13 @@ dependencies: "@storybook/global" "^5.0.0" +"@storybook/client-logger@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.12.tgz#ee571b22e6f31a3d2fd1ad357a5725f46cfb6ded" + integrity sha512-hiRv6dXsOttMPqm9SxEuFoAtDe9rs7TUS8XcO5rmJ9BgfwBJsYlHzAxXkazxmvlyZtKL7gMx6m8OYbCdZgUqtA== + dependencies: + "@storybook/global" "^5.0.0" + "@storybook/client-logger@7.6.7": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-7.6.7.tgz#a2cb75a668c09bf091c1925c3403e3f2f8b1e4e1" @@ -5792,18 +5811,18 @@ dependencies: "@storybook/global" "^5.0.0" -"@storybook/codemod@7.6.10": - version "7.6.10" - resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.10.tgz#21cc0e69df6f57d567fc27264310f820662d62fa" - integrity sha512-pzFR0nocBb94vN9QCJLC3C3dP734ZigqyPmd0ZCDj9Xce2ytfHK3v1lKB6TZWzKAZT8zztauECYxrbo4LVuagw== +"@storybook/codemod@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-7.6.12.tgz#a450327ea43e6684d028968477d5f895c8905c93" + integrity sha512-4EI4Ah1cvz6gFkXOS/LGf23oN8LO6ABGpWwPQoMHpIV3wUkFWBwrKFUe/UAQZGptnM0VZRYx4grS82Hluw4XJA== dependencies: "@babel/core" "^7.23.2" "@babel/preset-env" "^7.23.2" "@babel/types" "^7.23.0" "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "7.6.10" - "@storybook/node-logger" "7.6.10" - "@storybook/types" "7.6.10" + "@storybook/csf-tools" "7.6.12" + "@storybook/node-logger" "7.6.12" + "@storybook/types" "7.6.12" "@types/cross-spawn" "^6.0.2" cross-spawn "^7.0.3" globby "^11.0.2" @@ -5865,6 +5884,35 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/core-common@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.12.tgz#a42bdcdb5c68aafcf57492666ad99cfe8261e3f9" + integrity sha512-kM9YiBBMM2x5v/oylL7gdO1PS4oehgJC21MivS9p5QZ8uuXKtCQ6UQvI3rzaV+1ZzUA4n+I8MyaMrNIQk8KDbw== + dependencies: + "@storybook/core-events" "7.6.12" + "@storybook/node-logger" "7.6.12" + "@storybook/types" "7.6.12" + "@types/find-cache-dir" "^3.2.1" + "@types/node" "^18.0.0" + "@types/node-fetch" "^2.6.4" + "@types/pretty-hrtime" "^1.0.0" + chalk "^4.1.0" + esbuild "^0.18.0" + esbuild-register "^3.5.0" + file-system-cache "2.3.0" + find-cache-dir "^3.0.0" + find-up "^5.0.0" + fs-extra "^11.1.0" + glob "^10.0.0" + handlebars "^4.7.7" + lazy-universal-dotenv "^4.0.0" + node-fetch "^2.0.0" + picomatch "^2.3.0" + pkg-dir "^5.0.0" + pretty-hrtime "^1.0.3" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/core-common@7.6.7": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-7.6.7.tgz#69801d7a70b4ed6dab5dec589f612814628d3807" @@ -5901,6 +5949,13 @@ dependencies: ts-dedent "^2.0.0" +"@storybook/core-events@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.12.tgz#b622a51ee905ca1adb83163a912bb9dcfee3ba4a" + integrity sha512-IO4cwk7bBCKH6lLnnIlHO9FwQXt/9CzLUAoZSY9msWsdPppCdKlw8ynJI5YarSNKDBUn8ArIfnRf0Mve0KQr9Q== + dependencies: + ts-dedent "^2.0.0" + "@storybook/core-events@7.6.7": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-7.6.7.tgz#ee8823090cc4e30fddebe72be29738e4b2e66b11" @@ -5908,26 +5963,26 @@ dependencies: ts-dedent "^2.0.0" -"@storybook/core-server@7.6.10": - version "7.6.10" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.10.tgz#53bf43b8b3c999c87196774a0b92e2e10a434e4c" - integrity sha512-2icnqJkn3vwq0eJPP0rNaHd7IOvxYf5q4lSVl2AWTxo/Ae19KhokI6j/2vvS2XQJMGQszwshlIwrZUNsj5p0yw== +"@storybook/core-server@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-7.6.12.tgz#486d022758dc7bbcc088e3d8d454404464f568dc" + integrity sha512-tjWifKsDnIc8pvbjVyQrOHef70Gcp93Bg3WwuysB8PGk7lcX2RD9zv44HNIyjxdOLSSv66IGKrOldEBL3hab4w== dependencies: "@aw-web-design/x-default-browser" "1.4.126" "@discoveryjs/json-ext" "^0.5.3" - "@storybook/builder-manager" "7.6.10" - "@storybook/channels" "7.6.10" - "@storybook/core-common" "7.6.10" - "@storybook/core-events" "7.6.10" + "@storybook/builder-manager" "7.6.12" + "@storybook/channels" "7.6.12" + "@storybook/core-common" "7.6.12" + "@storybook/core-events" "7.6.12" "@storybook/csf" "^0.1.2" - "@storybook/csf-tools" "7.6.10" + "@storybook/csf-tools" "7.6.12" "@storybook/docs-mdx" "^0.1.0" "@storybook/global" "^5.0.0" - "@storybook/manager" "7.6.10" - "@storybook/node-logger" "7.6.10" - "@storybook/preview-api" "7.6.10" - "@storybook/telemetry" "7.6.10" - "@storybook/types" "7.6.10" + "@storybook/manager" "7.6.12" + "@storybook/node-logger" "7.6.12" + "@storybook/preview-api" "7.6.12" + "@storybook/telemetry" "7.6.12" + "@storybook/types" "7.6.12" "@types/detect-port" "^1.3.0" "@types/node" "^18.0.0" "@types/pretty-hrtime" "^1.0.0" @@ -5989,6 +6044,21 @@ recast "^0.23.1" ts-dedent "^2.0.0" +"@storybook/csf-tools@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.12.tgz#42ef641a2bcc2feaff167d68ea5b58aebe4f087c" + integrity sha512-MdhkYYxSW5I6Jpk34gTkAZsuj9sxe0xdyeUQpNa8CgJxG43F+ehZ6scW/IPjoSG9gCXBUJMekq26UrmbVfsLCQ== + dependencies: + "@babel/generator" "^7.23.0" + "@babel/parser" "^7.23.0" + "@babel/traverse" "^7.23.2" + "@babel/types" "^7.23.0" + "@storybook/csf" "^0.1.2" + "@storybook/types" "7.6.12" + fs-extra "^11.1.0" + recast "^0.23.1" + ts-dedent "^2.0.0" + "@storybook/csf-tools@7.6.7": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-7.6.7.tgz#1707bc5d6289ec79aeab472877aadda76def5015" @@ -6074,10 +6144,10 @@ telejson "^7.2.0" ts-dedent "^2.0.0" -"@storybook/manager@7.6.10": - version "7.6.10" - resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.10.tgz#eb1b71c802fbf04353f3bf017dfb102eb0db217e" - integrity sha512-Co3sLCbNYY6O4iH2ggmRDLCPWLj03JE5s/DOG8OVoXc6vBwTc/Qgiyrsxxp6BHQnPpM0mxL6aKAxE3UjsW/Nog== +"@storybook/manager@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/manager/-/manager-7.6.12.tgz#147219c57f4b68d343a9e0ee1563e5214cd09549" + integrity sha512-WMWvswJHGiqJFJb98WQMQfZQhLuVtmci4y/VJGQ/Jnq1nJQs76BCtaeGiHcsYmRaAP1HMI4DbzuTSEgca146xw== "@storybook/mdx2-csf@^1.0.0": version "1.1.0" @@ -6089,6 +6159,11 @@ resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.10.tgz#d4c52d04384d2728d6610fb0afff6eb1feb50fd4" integrity sha512-ZBuqrv4bjJzKXyfRGFkVIi+z6ekn6rOPoQao4KmsfLNQAUUsEdR8Baw/zMnnU417zw5dSEaZdpuwx75SCQAeOA== +"@storybook/node-logger@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.12.tgz#2232fc45ca8439649d8cb2cefe38f0a97c1aa275" + integrity sha512-iS44/EjfF6hLecKzICmcpQoB9bmVi4tXx5gVXnbI5ZyziBibRQcg/U191Njl7wY2ScN/RCQOr8lh5k57rI3Prg== + "@storybook/node-logger@7.6.7", "@storybook/node-logger@^7.0.12": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-7.6.7.tgz#35cee2b3e4d234b0b0735715d8856dc141d4a9b0" @@ -6142,6 +6217,26 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/preview-api@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/preview-api/-/preview-api-7.6.12.tgz#d431cc76d733c17ba1943a31fc3297de8f40c467" + integrity sha512-uSzeMSLnCRROjiofJP0F0niLWL+sboQ5ktHW6BAYoPwprumXduPxKBUVEZNxMbVYoAz9v/kEZmaLauh8LRP2Hg== + dependencies: + "@storybook/channels" "7.6.12" + "@storybook/client-logger" "7.6.12" + "@storybook/core-events" "7.6.12" + "@storybook/csf" "^0.1.2" + "@storybook/global" "^5.0.0" + "@storybook/types" "7.6.12" + "@types/qs" "^6.9.5" + dequal "^2.0.2" + lodash "^4.17.21" + memoizerific "^1.11.3" + qs "^6.10.0" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/preview@7.6.10": version "7.6.10" resolved "https://registry.yarnpkg.com/@storybook/preview/-/preview-7.6.10.tgz#895053c97f7e09141c6321fa42390fa8af377bef" @@ -6211,14 +6306,14 @@ memoizerific "^1.11.3" qs "^6.10.0" -"@storybook/telemetry@7.6.10": - version "7.6.10" - resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.10.tgz#31c0edfb9c7005cf9b5922e51ca896218e3d81ea" - integrity sha512-p3mOSUtIyy2tF1z6pQXxNh1JzYFcAm97nUgkwLzF07GfEdVAPM+ftRSLFbD93zVvLEkmLTlsTiiKaDvOY/lQWg== +"@storybook/telemetry@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-7.6.12.tgz#8a49317466c98a184cd01ad6c53162ee1c05a626" + integrity sha512-eBG3sLb9CZ05pyK2JXBvnaAsxDzbZH57VyhtphhuZmx0DqF/78qIoHs9ebRJpJWV0sL5rtT9vIq8QXpQhDHLWg== dependencies: - "@storybook/client-logger" "7.6.10" - "@storybook/core-common" "7.6.10" - "@storybook/csf-tools" "7.6.10" + "@storybook/client-logger" "7.6.12" + "@storybook/core-common" "7.6.12" + "@storybook/csf-tools" "7.6.12" chalk "^4.1.0" detect-package-manager "^2.0.1" fetch-retry "^5.0.2" @@ -6277,6 +6372,16 @@ "@types/express" "^4.7.0" file-system-cache "2.3.0" +"@storybook/types@7.6.12": + version "7.6.12" + resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.12.tgz#af7813e6f4ca31c500f9e28af5f591c8b1ea1b13" + integrity sha512-Wsbd+NS10/2yMHQ/26rXHflXam0hm2qufTFiHOX6VXZWxij3slRU88Fnwzp+1QSyjXb0qkEr8dOx7aG00+ItVw== + dependencies: + "@storybook/channels" "7.6.12" + "@types/babel__core" "^7.0.0" + "@types/express" "^4.7.0" + file-system-cache "2.3.0" + "@storybook/types@7.6.7": version "7.6.7" resolved "https://registry.yarnpkg.com/@storybook/types/-/types-7.6.7.tgz#f3935fbd3ba7f958e18106fd1626452a8961ef8c" @@ -18924,12 +19029,12 @@ store2@^2.14.2: resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.2.tgz#56138d200f9fe5f582ad63bc2704dbc0e4a45068" integrity sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w== -storybook@7.6.10: - version "7.6.10" - resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.10.tgz#2185d26cd7b43390e3e2c7581586e2f60cdbd9bd" - integrity sha512-ypFeGhQTUBBfqSUVZYh7wS5ghn3O2wILCiQc4459SeUpvUn+skcqw/TlrwGSoF5EWjDA7gtRrWDxO3mnlPt5Cw== +storybook@7.6.12: + version "7.6.12" + resolved "https://registry.yarnpkg.com/storybook/-/storybook-7.6.12.tgz#63a45b2a32f204abb77c8c20ba85ecba21990500" + integrity sha512-zcH9CwIsE8N4PX3he5vaJ3mTTWGxu7cxJ/ag9oja/k3N5/IvQjRyIU1TLkRVb28BB8gaLyorpnc4C4aLVGy4WQ== dependencies: - "@storybook/cli" "7.6.10" + "@storybook/cli" "7.6.12" stream-browserify@3.0.0: version "3.0.0"