From 6c697324a6b9f647f8a4e4b00c27d43ce501b3b1 Mon Sep 17 00:00:00 2001 From: Douglas Daniel Date: Tue, 7 Jan 2025 13:22:04 -0600 Subject: [PATCH] feat(wallet): Prompt Select Account when Sending from Portfolio --- .../desktop/wallet-menus/asset-item-menu.tsx | 8 +-- .../select_token_modal/select_token_modal.tsx | 54 +++++++++++++++---- .../screens/send/send_screen/send_screen.tsx | 50 +++++++++++------ .../brave_wallet_ui/utils/routes-utils.ts | 15 +++--- 4 files changed, 87 insertions(+), 40 deletions(-) diff --git a/components/brave_wallet_ui/components/desktop/wallet-menus/asset-item-menu.tsx b/components/brave_wallet_ui/components/desktop/wallet-menus/asset-item-menu.tsx index 31931a2f59e2..725b3138cf22 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-menus/asset-item-menu.tsx +++ b/components/brave_wallet_ui/components/desktop/wallet-menus/asset-item-menu.tsx @@ -7,7 +7,7 @@ import * as React from 'react' import { useHistory } from 'react-router' // Types -import { BraveWallet, WalletRoutes } from '../../../constants/types' +import { BraveWallet } from '../../../constants/types' // Queries import { @@ -106,11 +106,7 @@ export const AssetItemMenu = (props: Props) => { }, [foundMeldBuyToken, history, account, foundAndroidBuyToken, asset]) const onClickSend = React.useCallback(() => { - if (account) { - history.push(makeSendRoute(asset, account)) - } else { - history.push(WalletRoutes.Send) - } + history.push(makeSendRoute(asset, account)) }, [account, history, asset]) const onClickSwap = React.useCallback(() => { diff --git a/components/brave_wallet_ui/page/screens/composer_ui/select_token_modal/select_token_modal.tsx b/components/brave_wallet_ui/page/screens/composer_ui/select_token_modal/select_token_modal.tsx index 9331627b561e..177b9189bccb 100644 --- a/components/brave_wallet_ui/page/screens/composer_ui/select_token_modal/select_token_modal.tsx +++ b/components/brave_wallet_ui/page/screens/composer_ui/select_token_modal/select_token_modal.tsx @@ -6,6 +6,7 @@ import * as React from 'react' import Icon from '@brave/leo/react/icon' import { skipToken } from '@reduxjs/toolkit/query/react' +import { useHistory } from 'react-router' // Selectors import { @@ -19,7 +20,11 @@ import { useSafeUISelector } from '../../../../common/hooks/use-safe-selector' import { UISelectors } from '../../../../common/selectors' // Types -import { BraveWallet, SendPageTabHashes } from '../../../../constants/types' +import { + BraveWallet, + SendPageTabHashes, + WalletRoutes +} from '../../../../constants/types' import { TokenBalancesRegistry // } from '../../../../common/slices/entities/token-balance.entity' @@ -172,6 +177,7 @@ interface Props { onSelectSendOption?: (sendOption: SendPageTabHashes) => void selectedNetwork?: BraveWallet.NetworkInfo modalType: 'send' | 'swap' | 'bridge' + needsAccount?: boolean } export const SelectTokenModal = React.forwardRef( @@ -185,9 +191,13 @@ export const SelectTokenModal = React.forwardRef( onSelectAsset, onSelectSendOption, selectedNetwork, - modalType + modalType, + needsAccount } = props + // Routing + const history = useHistory() + // State const [searchValue, setSearchValue] = React.useState('') const [selectedNetworkFilter, setSelectedNetworkFilter] = @@ -196,13 +206,17 @@ export const SelectTokenModal = React.forwardRef( ) const [selectedAccountFilter, setSelectedAccountFilter] = React.useState(AllAccountsOption) - const [pendingSelectedAsset, setPendingSelectedAsset] = React.useState< - BraveWallet.BlockchainToken | undefined - >(undefined) + const [pendingSelectedAssetState, setPendingSelectedAssetState] = + React.useState(undefined) const [tokenDetails, setTokenDetails] = React.useState< BraveWallet.BlockchainToken | undefined >(undefined) + // Computed + const pendingSelectedAsset = needsAccount + ? selectedFromToken + : pendingSelectedAssetState + // Selectors const isPanel = useSafeUISelector(UISelectors.isPanel) @@ -502,7 +516,7 @@ export const SelectTokenModal = React.forwardRef( return } - setPendingSelectedAsset(token) + setPendingSelectedAssetState(token) }, [onSelectAsset, onClose, selectingFromOrTo, modalType] ) @@ -618,6 +632,24 @@ export const SelectTokenModal = React.forwardRef( [accounts, checkIsAccountOptionDisabled] ) + const handleOnClose = React.useCallback(() => { + // Ensure we clear route params if an account is not selected + // to prevent a broken state. + if (needsAccount && modalType === 'send') { + history.replace(WalletRoutes.Send) + } + onClose() + }, [modalType, needsAccount, history, onClose]) + + const handleOnBack = React.useCallback(() => { + // Clears route params if an account is not selected + // and user clicks back. + if (needsAccount && modalType === 'send') { + history.replace(WalletRoutes.Send) + } + setPendingSelectedAssetState(undefined) + }, [modalType, needsAccount, history]) + // Computed & Memos const emptyTokensList = !isLoadingBalances && tokensBySearchValue.length === 0 @@ -688,8 +720,8 @@ export const SelectTokenModal = React.forwardRef( return ( setPendingSelectedAsset(undefined)} + onClose={handleOnClose} + onBack={handleOnBack} width='560px' showDivider={false} > @@ -715,7 +747,7 @@ export const SelectTokenModal = React.forwardRef( return ( setTokenDetails(undefined)} width='560px' showDivider={false} @@ -728,7 +760,7 @@ export const SelectTokenModal = React.forwardRef( return ( <> ( {isPanel && ( setPendingSelectedAsset(undefined)} + onClose={handleOnBack} isOpen={pendingSelectedAsset !== undefined} > {pendingSelectedAsset && ( diff --git a/components/brave_wallet_ui/page/screens/send/send_screen/send_screen.tsx b/components/brave_wallet_ui/page/screens/send/send_screen/send_screen.tsx index 3d830349568a..bfe898f0d752 100644 --- a/components/brave_wallet_ui/page/screens/send/send_screen/send_screen.tsx +++ b/components/brave_wallet_ui/page/screens/send/send_screen/send_screen.tsx @@ -107,20 +107,17 @@ export const SendScreen = React.memo((props: Props) => { const history = useHistory() const { hash } = useLocation() const selectedSendOption = (hash as SendPageTabHashes) || '#token' + const accountIdFromParams = query.get('account') ?? undefined + const chainIdFromParams = query.get('chainId') ?? undefined + const contractOrSymbolFromParams = query.get('token') ?? undefined - const { account: accountFromParams } = useAccountFromAddressQuery( - query.get('account') ?? undefined - ) + const { account: accountFromParams } = + useAccountFromAddressQuery(accountIdFromParams) const { data: networks = [] } = useGetVisibleNetworksQuery() const networkFromParams = React.useMemo( - () => - networks.find( - (network) => - network.chainId === query.get('chainId') && - network.coin === accountFromParams?.accountId.coin - ), - [networks, accountFromParams, query] + () => networks.find((network) => network.chainId === chainIdFromParams), + [networks, chainIdFromParams] ) // State @@ -178,8 +175,7 @@ export const SendScreen = React.memo((props: Props) => { return } - const contractOrSymbol = query.get('token') - if (!contractOrSymbol) { + if (!contractOrSymbolFromParams) { return } @@ -191,18 +187,24 @@ export const SendScreen = React.memo((props: Props) => { tokenId ? token.chainId === networkFromParams.chainId && token.contractAddress.toLowerCase() === - contractOrSymbol.toLowerCase() && + contractOrSymbolFromParams.toLowerCase() && token.tokenId === tokenId && token.isShielded === isShielded : (token.chainId === networkFromParams.chainId && token.contractAddress.toLowerCase() === - contractOrSymbol.toLowerCase()) || + contractOrSymbolFromParams.toLowerCase()) || (token.chainId === networkFromParams.chainId && token.contractAddress === '' && - token.symbol.toLowerCase() === contractOrSymbol.toLowerCase()) && - token.isShielded === isShielded + token.symbol.toLowerCase() === + contractOrSymbolFromParams.toLowerCase() && + token.isShielded === isShielded) ) - }, [userVisibleTokensInfo, query, networkFromParams]) + }, [ + userVisibleTokensInfo, + query, + networkFromParams, + contractOrSymbolFromParams + ]) const { data: tokenBalancesRegistry, isLoading: isLoadingBalances } = useScopedBalanceUpdater( @@ -258,6 +260,11 @@ export const SendScreen = React.memo((props: Props) => { return getDominantColorFromImageURL(tokenFromParams?.logo ?? '') }, [tokenFromParams?.logo]) + const needsAccountSelected = + accountIdFromParams === undefined && + contractOrSymbolFromParams !== undefined && + chainIdFromParams !== undefined + // Methods const selectSendAsset = React.useCallback( (asset: BraveWallet.BlockchainToken, account?: BraveWallet.AccountInfo) => { @@ -508,6 +515,13 @@ export const SendScreen = React.memo((props: Props) => { isModalShown: showSelectAddressModal } = useModal() + // Effects + React.useEffect(() => { + if (needsAccountSelected) { + openSelectTokenModal() + } + }, [needsAccountSelected, openSelectTokenModal]) + // render return ( <> @@ -644,6 +658,8 @@ export const SendScreen = React.memo((props: Props) => { ref={selectTokenModalRef} onSelectAsset={selectSendAsset} onSelectSendOption={onSelectSendOption} + selectedFromToken={needsAccountSelected ? tokenFromParams : undefined} + needsAccount={needsAccountSelected} selectingFromOrTo='from' modalType='send' /> diff --git a/components/brave_wallet_ui/utils/routes-utils.ts b/components/brave_wallet_ui/utils/routes-utils.ts index ad70d09b0668..0dd983750463 100644 --- a/components/brave_wallet_ui/utils/routes-utils.ts +++ b/components/brave_wallet_ui/utils/routes-utils.ts @@ -226,22 +226,25 @@ export const makeDepositFundsAccountRoute = (assetId: string) => { export const makeSendRoute = ( asset: BraveWallet.BlockchainToken, - account: BraveWallet.AccountInfo + account?: BraveWallet.AccountInfo ) => { const isNftTab = asset.isErc721 || asset.isNft const baseQueryParams = { chainId: asset.chainId, - token: asset.contractAddress || asset.symbol.toUpperCase(), - account: account.accountId.uniqueKey + token: asset.contractAddress || asset.symbol.toUpperCase() } - const tokenIdQueryParams = asset.tokenId - ? { ...baseQueryParams, tokenId: asset.tokenId } + const accountIdQueryParams = account + ? { ...baseQueryParams, account: account.accountId.uniqueKey } : baseQueryParams + const tokenIdQueryParams = asset.tokenId + ? { ...accountIdQueryParams, tokenId: asset.tokenId } + : accountIdQueryParams + const params = new URLSearchParams( asset.isShielded - ? {...tokenIdQueryParams, isShielded: 'true'} + ? { ...tokenIdQueryParams, isShielded: 'true' } : tokenIdQueryParams )