diff --git a/browser/ui/webui/brave_wallet/android/android_wallet_page_ui.cc b/browser/ui/webui/brave_wallet/android/android_wallet_page_ui.cc index e8b99afe5d9b..0d4d1fa8bc02 100644 --- a/browser/ui/webui/brave_wallet/android/android_wallet_page_ui.cc +++ b/browser/ui/webui/brave_wallet/android/android_wallet_page_ui.cc @@ -70,10 +70,10 @@ AndroidWalletPageUI::AndroidWalletPageUI(content::WebUI* web_ui, kUntrustedLineChartURL + " " + kUntrustedMarketURL + ";"); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ImgSrc, - base::JoinString( - {"img-src", "'self'", "chrome://resources", - "chrome://erc-token-images", base::StrCat({"data:", ";"})}, - " ")); + base::JoinString({"img-src", "'self'", "chrome://resources", + "chrome://erc-token-images", "chrome://image", + base::StrCat({"data:", ";"})}, + " ")); source->AddString("braveWalletTrezorBridgeUrl", kUntrustedTrezorURL); source->AddString("braveWalletNftBridgeUrl", kUntrustedNftURL); source->AddString("braveWalletLineChartBridgeUrl", kUntrustedLineChartURL); diff --git a/components/brave_wallet/browser/android_page_appearing_browsertest.cc b/components/brave_wallet/browser/android_page_appearing_browsertest.cc index f4cc3d1fecc5..fea85a7ceff9 100644 --- a/components/brave_wallet/browser/android_page_appearing_browsertest.cc +++ b/components/brave_wallet/browser/android_page_appearing_browsertest.cc @@ -437,7 +437,9 @@ IN_PROC_BROWSER_TEST_F(AndroidPageAppearingBrowserTest, TestBuyPageAppearing) { GURL url = GURL(base::StrCat({scheme, "wallet/crypto/fund-wallet"})); const std::vector ignore_patterns = { "TypeError: Cannot read properties of undefined (reading 'forEach')", - "ReactDOM.render is no longer supported in React 18"}; + "ReactDOM.render is no longer supported in React 18", + "Error getting default country", + "Error: An internal error has occurred"}; VerifyPage(url, expected_url, ignore_patterns); } } diff --git a/components/brave_wallet/browser/brave_wallet_constants.h b/components/brave_wallet/browser/brave_wallet_constants.h index c6dfba64b66e..a4e1e6a4da5b 100644 --- a/components/brave_wallet/browser/brave_wallet_constants.h +++ b/components/brave_wallet/browser/brave_wallet_constants.h @@ -1054,6 +1054,28 @@ inline constexpr webui::LocalizedString kLocalizedStrings[] = { {"braveWalletBuyWithRamp", IDS_BRAVE_WALLET_BUY_WITH_RAMP}, {"braveWalletSellWithProvider", IDS_BRAVE_WALLET_SELL_WITH_PROVIDER}, {"braveWalletBuyDisclaimer", IDS_BRAVE_WALLET_BUY_DISCLAIMER}, + {"braveWalletTransactionsPartner", IDS_BRAVE_WALLET_TRANSACTIONS_PARTNER}, + {"braveWalletTransactionPartnerConsent", + IDS_BRAVE_WALLET_TRANSACTION_PARTNER_CONSENT}, + {"braveWalletMeldTermsOfUse", IDS_BRAVE_WALLET_MELD_TERMS_OF_USE}, + {"braveWalletBestOption", IDS_BRAVE_WALLET_BEST_OPTION}, + {"braveWalletExchangeRateWithFees", + IDS_BRAVE_WALLET_EXCHANGE_RATE_WITH_FEES}, + {"braveWalletFees", IDS_BRAVE_WALLET_FEES}, + {"braveWalletPriceCurrency", IDS_BRAVE_WALLET_PRICE_CURRENCY}, + {"braveWalletBuyWithProvider", IDS_BRAVE_WALLET_BUY_WITH_PROVIDER}, + {"braveWalletAsset", IDS_BRAVE_WALLET_ASSETS}, + {"braveWalletSelected", IDS_BRAVE_WALLET_SELECTED}, + {"braveWalletNoAvailableCurrencies", + IDS_BRAVE_WALLET_NO_AVAILABLE_CURRENCIES}, + {"braveWalletGettingBestPrices", IDS_BRAVE_WALLET_GETTING_BEST_PRICES}, + {"braveWalletBuyAsset", IDS_BRAVE_WALLET_BUY_ASSET}, + {"braveWalletNoProviderFound", IDS_BRAVE_WALLET_NO_PROVIDER_FOUND}, + {"braveWalletTrySearchingForDifferentAsset", + IDS_BRAVE_WALLET_TRY_SEARCHING_FOR_DIFFERENT_ASSET}, + {"braveWalletNoResultsFound", IDS_BRAVE_WALLET_NO_RESULTS_FOUND}, + {"braveWalletTryDifferentKeywords", + IDS_BRAVE_WALLET_TRY_DIFFERENT_KEYWORDS}, {"braveWalletBuyWithSardine", IDS_BRAVE_WALLET_BUY_WITH_SARDINE}, {"braveWalletBuyWithTransak", IDS_BRAVE_WALLET_BUY_WITH_TRANSAK}, {"braveWalletBuyWithStripe", IDS_BRAVE_WALLET_BUY_WITH_STRIPE}, diff --git a/components/brave_wallet_ui/common/hooks/use-multi-chain-buy-assets.ts b/components/brave_wallet_ui/common/hooks/use-multi-chain-buy-assets.ts index f5824ca5fc58..97f4e2d7bc24 100644 --- a/components/brave_wallet_ui/common/hooks/use-multi-chain-buy-assets.ts +++ b/components/brave_wallet_ui/common/hooks/use-multi-chain-buy-assets.ts @@ -8,24 +8,49 @@ import { skipToken } from '@reduxjs/toolkit/query/react' // types import { BraveWallet } from '../../constants/types' +// utils +import { getAssetSymbol } from '../../utils/meld_utils' + // hooks -import { useGetOnRampAssetsQuery } from '../slices/api.slice' +import { useGetMeldCryptoCurrenciesQuery } from '../slices/api.slice' -export const useIsBuySupported = ( - token?: Pick +export const useFindBuySupportedToken = ( + token?: Pick< + BraveWallet.BlockchainToken, + 'symbol' | 'contractAddress' | 'chainId' + > ) => { // queries - const { data: options = undefined } = useGetOnRampAssetsQuery( + const { data: options } = useGetMeldCryptoCurrenciesQuery( token ? undefined : skipToken ) // computed - const isBuySupported = + const foundNativeToken = + token && + token.contractAddress === '' && + options?.find( + (asset) => + asset.chainId?.toLowerCase() === token.chainId.toLowerCase() && + getAssetSymbol(asset).toLowerCase() === token.symbol.toLowerCase() + ) + + const foundTokenByContractAddress = + token && + options?.find( + (asset) => + asset.contractAddress?.toLowerCase() === + token.contractAddress.toLowerCase() && + asset.chainId?.toLowerCase() === token.chainId.toLowerCase() + ) + + const foundTokenBySymbol = token && - options?.allAssetOptions.some( - (asset) => asset.symbol.toLowerCase() === token.symbol.toLowerCase() + options?.find( + (asset) => + getAssetSymbol(asset).toLowerCase() === token.symbol.toLowerCase() ) // render - return isBuySupported + return foundNativeToken || foundTokenByContractAddress || foundTokenBySymbol } diff --git a/components/brave_wallet_ui/common/slices/endpoints/meld_integration.endpoints.ts b/components/brave_wallet_ui/common/slices/endpoints/meld_integration.endpoints.ts index a9037c625ccc..61d639b68c7e 100644 --- a/components/brave_wallet_ui/common/slices/endpoints/meld_integration.endpoints.ts +++ b/components/brave_wallet_ui/common/slices/endpoints/meld_integration.endpoints.ts @@ -3,6 +3,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. +// Types import { MeldCountry, MeldCryptoCurrency, @@ -10,22 +11,29 @@ import { MeldFilter, MeldCryptoQuote, MeldServiceProvider, - SupportedOnRampNetworks, MeldPaymentMethod, CryptoWidgetCustomerData, CryptoBuySessionData, MeldCryptoWidget } from '../../../constants/types' + +// Utils import { handleEndpointError } from '../../../utils/api-utils' +import { getMeldTokensChainId } from '../../../utils/meld_utils' import { WalletApiEndpointBuilderParams } from '../api-base.slice' type GetCryptoQuotesArgs = { country: string sourceCurrencyCode: string - destionationCurrencyCode: string + destinationCurrencyCode: string amount: number account: string | null - paymentMethods: MeldPaymentMethod[] + paymentMethod: MeldPaymentMethod +} + +type GetPaymentMethodsArg = { + country: string + sourceCurrencyCode: string } type CreateMeldBuyWidgetArgs = { @@ -33,6 +41,22 @@ type CreateMeldBuyWidgetArgs = { customerData: CryptoWidgetCustomerData } +const supportedChains = [ + 'BTC', + 'FIL', + 'ZEC', + 'ETH', + 'SOLANA', + 'FTM', + 'BSC', + 'POLYGON', + 'OPTIMISM', + 'AURORA', + 'CELO', + 'ARBITRUM', + 'AVAXC' +] + export const meldIntegrationEndpoints = ({ query, mutation @@ -90,16 +114,17 @@ export const meldIntegrationEndpoints = ({ serviceProviders: undefined, paymentMethodTypes: undefined, statuses: undefined, - cryptoChains: undefined + cryptoChains: supportedChains.join(',') } const { fiatCurrencies: cryptoCurrencies, error } = await meldIntegrationService.getCryptoCurrencies(filter) - const supportedAssets = cryptoCurrencies?.filter((asset) => - SupportedOnRampNetworks.includes( - '0x' + parseInt(asset?.chainId ?? '').toString(16) - ) - ) + const tokenList = cryptoCurrencies?.map((token) => { + return { + ...token, + chainId: getMeldTokensChainId(token) + } + }) if (error) { return handleEndpointError( @@ -110,7 +135,7 @@ export const meldIntegrationEndpoints = ({ } return { - data: supportedAssets || [] + data: tokenList || [] } } catch (error) { return handleEndpointError( @@ -126,8 +151,6 @@ export const meldIntegrationEndpoints = ({ queryFn: async (_arg, { endpoint }, _extraOptions, baseQuery) => { try { const defaultCountry: string = await new Promise((resolve) => { - // TODO(william): implement this for wallet - // to avoid using braveRewards api chrome.braveRewards.getDefaultCountry((defaultCountry) => { resolve(defaultCountry) }) @@ -237,18 +260,19 @@ export const meldIntegrationEndpoints = ({ const { country, sourceCurrencyCode, - destionationCurrencyCode, + destinationCurrencyCode, amount, - account + account, + paymentMethod } = params const result = await meldIntegrationService.getCryptoQuotes( country, sourceCurrencyCode, - destionationCurrencyCode, + destinationCurrencyCode, amount, account, - null + paymentMethod.paymentMethod ) return { @@ -264,14 +288,14 @@ export const meldIntegrationEndpoints = ({ }, invalidatesTags: ['MeldCryptoQuotes'] }), - getMeldPaymentMethods: query({ - queryFn: async (_arg, { endpoint }, _extraOptions, baseQuery) => { + getMeldPaymentMethods: query({ + queryFn: async (params, { endpoint }, _extraOptions, baseQuery) => { try { const { meldIntegrationService } = baseQuery(undefined).data - + const { country, sourceCurrencyCode } = params const filter: MeldFilter = { - countries: undefined, - fiatCurrencies: undefined, + countries: country, + fiatCurrencies: sourceCurrencyCode, cryptoCurrencies: undefined, serviceProviders: undefined, paymentMethodTypes: undefined, diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.stories.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.stories.tsx index aa6e5e548980..61b2d26a2848 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.stories.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.stories.tsx @@ -4,8 +4,12 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' + +// Components import { PartnersConsentModal } from './partners_consent_modal' -import WalletPageStory from '../../../../stories/wrappers/wallet-page-story-wrapper' +import { + WalletPageStory // +} from '../../../../stories/wrappers/wallet-page-story-wrapper' export const _PartnersConsentModal = () => { const [isOpen, setIsOpen] = React.useState(true) diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.style.ts b/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.style.ts index 546511a99f2f..dd42a41df04e 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.style.ts +++ b/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.style.ts @@ -7,6 +7,9 @@ import styled from 'styled-components' import Dialog from '@brave/leo/react/dialog' import { font, spacing, color } from '@brave/leo/tokens/css/variables' +// Shared Styles +import { WalletButton } from '../../../shared/style' + export const TermsDialog = styled(Dialog)` --leo-dialog-backdrop-background: rgba(17, 18, 23, 0.35); --leo-dialog-backdrop-filter: blur(8px); @@ -29,10 +32,17 @@ export const TermsLabel = styled.span` font: ${font.default.regular}; margin: 0; padding: 0; +` - & a { - color: ${color.text.interactive}; - text-decoration: none; - font: ${font.default.semibold}; - } +export const TermsButton = styled(WalletButton)` + font-family: Poppins; + color: ${color.text.interactive}; + text-decoration: none; + font: ${font.default.semibold}; + background: none; + cursor: pointer; + outline: none; + border: none; + margin: 0px; + padding: 0px; ` diff --git a/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.tsx b/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.tsx index e03c30c9c99e..cdcf29dff894 100644 --- a/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.tsx +++ b/components/brave_wallet_ui/components/desktop/popup-modals/partners_consent_modal/partners_consent_modal.tsx @@ -5,24 +5,28 @@ import * as React from 'react' import Checkbox from '@brave/leo/react/checkbox' +import Button from '@brave/leo/react/button' + +// Utils +import { getLocale, splitStringForTag } from '../../../../../common/locale' -// selectors +// Selectors import { useSafeUISelector } from '../../../../common/hooks/use-safe-selector' import { UISelectors } from '../../../../common/selectors' -// assets +// Assets import PageTermsGraphic from './assets/page_terms_graphic.svg' import PanelTermsGraphic from './assets/panel_terms_graphic.svg' -// styles +// Styled Components import { TermsText, TermsDialog, Title, - TermsLabel + TermsLabel, + TermsButton } from './partners_consent_modal.style' import { Row } from '../../../shared/style' -import Button from '@brave/leo/react/button' interface PartnerConsentModalProps { isOpen: boolean @@ -31,18 +35,37 @@ interface PartnerConsentModalProps { onCancel: () => void } -export function PartnersConsentModal({ - isOpen, - onClose, - onContinue, - onCancel -}: PartnerConsentModalProps) { +export function PartnersConsentModal( + props: Readonly +) { + const { isOpen, onCancel, onClose, onContinue } = props + // state const [termsAccepted, setTermsAccepted] = React.useState(false) // redux const isPanel = useSafeUISelector(UISelectors.isPanel) + const { beforeTag, duringTag } = splitStringForTag( + getLocale('braveWalletMeldTermsOfUse'), + 1 + ) + + const onClickTermsOfUse = () => { + chrome.tabs.create( + { + url: 'https://www.meld.io/terms-of-use' + }, + () => { + if (chrome.runtime.lastError) { + console.error( + 'tabs.create failed: ' + chrome.runtime.lastError.message + ) + } + } + ) + } + return ( - Transactions Partner + {getLocale('braveWalletTransactionsPartner')} Terms Graphic - - Buying or selling crypto in Brave Wallet uses Meld.io — an - on-ramp/off-ramp aggregator that provides a smooth experience and the - best available pricing. Your information will be shared with Meld.io to - complete the transaction. - + {getLocale('braveWalletTransactionPartnerConsent')} setTermsAccepted(e.checked)} > - I have read and agree to the{' '} - - Terms of use - + {beforeTag} + {duringTag} - Cancel + {getLocale('braveWalletButtonCancel')} diff --git a/components/brave_wallet_ui/components/desktop/views/market/index.tsx b/components/brave_wallet_ui/components/desktop/views/market/index.tsx index deca4403960b..51346b80c777 100644 --- a/components/brave_wallet_ui/components/desktop/views/market/index.tsx +++ b/components/brave_wallet_ui/components/desktop/views/market/index.tsx @@ -10,7 +10,7 @@ import { useHistory } from 'react-router' import { useGetCoinMarketQuery, useGetDefaultFiatCurrencyQuery, - useGetOnRampAssetsQuery + useGetMeldCryptoCurrenciesQuery } from '../../../../common/slices/api.slice' import { useGetCombinedTokensListQuery // @@ -42,6 +42,7 @@ import { makeFundWalletRoute } from '../../../../utils/routes-utils' import { getAssetIdKey } from '../../../../utils/asset-utils' +import { getAssetSymbol } from '../../../../utils/meld_utils' const assetsRequestLimit = 250 @@ -61,12 +62,7 @@ export const MarketView = () => { const { data: defaultFiatCurrency = 'usd' } = useGetDefaultFiatCurrencyQuery() const { data: combinedTokensList } = useGetCombinedTokensListQuery() - const { buyAssets } = useGetOnRampAssetsQuery(undefined, { - selectFromResult: (res) => ({ - isLoading: res.isLoading, - buyAssets: res.data?.allAssetOptions || [] - }) - }) + const { data: buyAssets } = useGetMeldCryptoCurrenciesQuery() const { data: allCoins = [], isLoading: isLoadingCoinMarketData } = useGetCoinMarketQuery({ @@ -91,25 +87,12 @@ export const MarketView = () => { case MarketUiCommand.SelectBuy: { const { payload } = message as SelectBuyMessage const symbolLower = payload.symbol.toLowerCase() - const foundTokens = buyAssets.filter( - (t) => t.symbol.toLowerCase() === symbolLower + const foundTokens = buyAssets?.filter( + (t) => getAssetSymbol(t) === symbolLower ) - if (foundTokens.length === 1) { - history.push( - makeFundWalletRoute(getAssetIdKey(foundTokens[0]), { - searchText: symbolLower - }) - ) - return - } - - if (foundTokens.length > 1) { - history.push( - makeFundWalletRoute('', { - searchText: symbolLower - }) - ) + if (foundTokens) { + history.push(makeFundWalletRoute(foundTokens[0])) } break } diff --git a/components/brave_wallet_ui/components/desktop/views/market/market_asset.tsx b/components/brave_wallet_ui/components/desktop/views/market/market_asset.tsx index ff2b67ac8af9..596e48073222 100644 --- a/components/brave_wallet_ui/components/desktop/views/market/market_asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/market/market_asset.tsx @@ -58,9 +58,7 @@ import { import { CoinStats } from '../portfolio/components/coin-stats/coin-stats' // Hooks -import { - useIsBuySupported // -} from '../../../../common/hooks/use-multi-chain-buy-assets' +import { useFindBuySupportedToken } from '../../../../common/hooks/use-multi-chain-buy-assets' import { useGetNetworkQuery, useGetPriceHistoryQuery, @@ -203,8 +201,9 @@ export const MarketAsset = () => { ) // custom hooks - const isAssetBuySupported = - useIsBuySupported(selectedAssetFromParams) && !isRewardsToken + const foundBuySupportedToken = useFindBuySupportedToken( + selectedAssetFromParams + ) // memos / computed const isLoadingGraphData = @@ -303,32 +302,10 @@ export const MarketAsset = () => { }, [history, selectedAssetFromParams, updateUserAssetVisible]) const onSelectBuy = React.useCallback(() => { - if (foundTokens.length === 1) { - history.push( - makeFundWalletRoute(getAssetIdKey(foundTokens[0]), { - searchText: foundTokens[0].symbol - }) - ) - return + if (foundBuySupportedToken) { + history.push(makeFundWalletRoute(foundBuySupportedToken)) } - - if (foundTokens.length > 1) { - history.push( - makeFundWalletRoute('', { - searchText: foundTokens[0].symbol - }) - ) - return - } - - if (selectedAssetFromParams) { - history.push( - makeFundWalletRoute(getAssetIdKey(selectedAssetFromParams), { - searchText: selectedAssetFromParams.symbol - }) - ) - } - }, [foundTokens, history, selectedAssetFromParams]) + }, [history, foundBuySupportedToken]) const onSelectDeposit = React.useCallback(() => { if (foundTokens.length === 1) { @@ -346,17 +323,8 @@ export const MarketAsset = () => { searchText: foundTokens[0].symbol }) ) - return - } - - if (selectedAssetFromParams) { - history.push( - makeFundWalletRoute('', { - searchText: selectedAssetFromParams.symbol - }) - ) } - }, [foundTokens, history, selectedAssetFromParams]) + }, [foundTokens, history]) // token list & market data needs to load before we can find an asset to // select from the url params @@ -418,7 +386,7 @@ export const MarketAsset = () => { /> - {isAssetBuySupported && ( + {foundBuySupportedToken && (
{getLocale('braveWalletBuy')} diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-fungible-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-fungible-asset.tsx index 6e688dc935d1..737b96b51afe 100644 --- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-fungible-asset.tsx +++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-fungible-asset.tsx @@ -71,9 +71,7 @@ import { import { useScopedBalanceUpdater // } from '../../../../common/hooks/use-scoped-balance-updater' -import { - useIsBuySupported // -} from '../../../../common/hooks/use-multi-chain-buy-assets' +import { useFindBuySupportedToken } from '../../../../common/hooks/use-multi-chain-buy-assets' import { useGetNetworkQuery, useGetTransactionsQuery, @@ -224,8 +222,9 @@ export const PortfolioFungibleAsset = () => { ) // custom hooks - const isAssetBuySupported = - useIsBuySupported(selectedAssetFromParams) && !isRewardsToken + const foundBuySupportedToken = useFindBuySupportedToken( + selectedAssetFromParams + ) // memos /** @@ -410,10 +409,10 @@ export const PortfolioFungibleAsset = () => { ]) const onSelectBuy = React.useCallback(() => { - if (selectedAssetFromParams) { - history.push(makeFundWalletRoute(getAssetIdKey(selectedAssetFromParams))) + if (foundBuySupportedToken) { + history.push(makeFundWalletRoute(foundBuySupportedToken)) } - }, [history, selectedAssetFromParams]) + }, [history, foundBuySupportedToken]) const onSelectDeposit = React.useCallback(() => { if (selectedAssetFromParams) { @@ -490,7 +489,7 @@ export const PortfolioFungibleAsset = () => { /> - {isAssetBuySupported && ( + {foundBuySupportedToken && !isRewardsToken && (
{getLocale('braveWalletBuy')} 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 30a45cb0562a..34a4ff9fdd92 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 @@ -11,7 +11,6 @@ import { BraveWallet, WalletRoutes } from '../../../constants/types' // Queries import { - useGetOnRampAssetsQuery, useUpdateUserAssetVisibleMutation // } from '../../../common/slices/api.slice' @@ -19,6 +18,9 @@ import { import { useMultiChainSellAssets // } from '../../../common/hooks/use-multi-chain-sell-assets' +import { + useFindBuySupportedToken // +} from '../../../common/hooks/use-multi-chain-buy-assets' // Utils import { getLocale } from '../../../../common/locale' @@ -65,8 +67,6 @@ export const AssetItemMenu = (props: Props) => { const [showSellModal, setShowSellModal] = React.useState(false) // Queries - const { data: { allAssetOptions: allBuyAssetOptions } = {} } = - useGetOnRampAssetsQuery() const [updateUserAssetVisible] = useUpdateUserAssetVisibleMutation() // Hooks @@ -79,21 +79,13 @@ export const AssetItemMenu = (props: Props) => { checkIsAssetSellSupported } = useMultiChainSellAssets() + const foundBuySupportedToken = useFindBuySupportedToken(asset) + // Memos const isAssetsBalanceZero = React.useMemo(() => { return new Amount(assetBalance).isZero() }, [assetBalance]) - const isBuySupported = React.useMemo(() => { - if (!allBuyAssetOptions || isAssetsBalanceZero) { - return false - } - return allBuyAssetOptions.some( - (buyableAsset) => - buyableAsset.symbol.toLowerCase() === asset.symbol.toLowerCase() - ) - }, [asset.symbol, allBuyAssetOptions, isAssetsBalanceZero]) - const isSwapSupported = coinSupportsSwap(asset.coin) && account !== undefined const isSellSupported = React.useMemo(() => { @@ -102,8 +94,10 @@ export const AssetItemMenu = (props: Props) => { // Methods const onClickBuy = React.useCallback(() => { - history.push(makeFundWalletRoute(getAssetIdKey(asset))) - }, [asset, history]) + if (foundBuySupportedToken) { + history.push(makeFundWalletRoute(foundBuySupportedToken, account)) + } + }, [foundBuySupportedToken, history, account]) const onClickSend = React.useCallback(() => { if (account) { @@ -149,7 +143,7 @@ export const AssetItemMenu = (props: Props) => { return ( - {isBuySupported && ( + {foundBuySupportedToken && ( {getLocale('braveWalletBuy')} diff --git a/components/brave_wallet_ui/components/desktop/wallet-page-wrapper/wallet-page-wrapper.style.ts b/components/brave_wallet_ui/components/desktop/wallet-page-wrapper/wallet-page-wrapper.style.ts index afd67570372a..7148dab15605 100644 --- a/components/brave_wallet_ui/components/desktop/wallet-page-wrapper/wallet-page-wrapper.style.ts +++ b/components/brave_wallet_ui/components/desktop/wallet-page-wrapper/wallet-page-wrapper.style.ts @@ -17,7 +17,7 @@ import LinesDark from './assets/portfolio_lines_background_dark.svg' // Shared Styles import { Row } from '../../shared/style' -const minCardHeight = 497 +const minCardHeight = 466 export const maxCardWidth = 768 const layoutSmallCardBottom = 67 export const layoutSmallWidth = 1100 diff --git a/components/brave_wallet_ui/components/shared/bottom_sheet/bottom_sheet.style.ts b/components/brave_wallet_ui/components/shared/bottom_sheet/bottom_sheet.style.ts index a15bd7f4f322..e4c3d4f0ae8d 100644 --- a/components/brave_wallet_ui/components/shared/bottom_sheet/bottom_sheet.style.ts +++ b/components/brave_wallet_ui/components/shared/bottom_sheet/bottom_sheet.style.ts @@ -22,7 +22,7 @@ export const Background = styled(Column)<{ isOpen: boolean }>` export const BottomCard = styled(Column)<{ isOpen: boolean }>` position: fixed; - bottom: ${(p) => (p.isOpen ? 0 : -600)}px; + bottom: ${(p) => (p.isOpen ? 0 : -800)}px; transition: all 0.3s ease-in-out; border-radius: 16px 16px 0px 0px; background-color: ${leo.color.container.background}; diff --git a/components/brave_wallet_ui/market/market-ui-messages.ts b/components/brave_wallet_ui/market/market-ui-messages.ts index 3364cdc53ea7..2b295431ec36 100644 --- a/components/brave_wallet_ui/market/market-ui-messages.ts +++ b/components/brave_wallet_ui/market/market-ui-messages.ts @@ -8,7 +8,7 @@ // you can obtain one at https://mozilla.org/MPL/2.0/. import { loadTimeData } from '../../common/loadTimeData' -import { BraveWallet } from '../constants/types' +import { BraveWallet, MeldCryptoCurrency } from '../constants/types' import { isComponentInStorybook } from '../utils/string-utils' const marketUiOrigin = loadTimeData.getString('braveWalletMarketUiBridgeUrl') @@ -55,7 +55,7 @@ export type SelectDepositMessage = MarketCommandMessage & { } export type UpdateBuyableAssetsMessage = MarketCommandMessage & { - payload: BraveWallet.BlockchainToken[] + payload: MeldCryptoCurrency[] | undefined } export type UpdateDepositableAssetsMessage = MarketCommandMessage & { diff --git a/components/brave_wallet_ui/market/market.tsx b/components/brave_wallet_ui/market/market.tsx index d0c163b8b397..884430d28eb8 100644 --- a/components/brave_wallet_ui/market/market.tsx +++ b/components/brave_wallet_ui/market/market.tsx @@ -27,6 +27,7 @@ import { BraveWallet, MarketAssetFilterOption, MarketGridColumnTypes, + MeldCryptoCurrency, SortOrder } from '../constants/types' @@ -50,6 +51,7 @@ import { searchCoinMarkets, sortCoinMarkets } from '../utils/coin-market-utils' +import { getAssetSymbol } from '../utils/meld_utils' // Options import { AssetFilterOptions } from '../options/market-data-filter-options' @@ -75,7 +77,7 @@ const App = () => { BraveWallet.CoinMarket[] >([]) const [buyableAssets, setBuyableAssets] = React.useState< - BraveWallet.BlockchainToken[] + MeldCryptoCurrency[] | undefined >([]) const [depositableAssets, setDepositableAssets] = React.useState< BraveWallet.BlockchainToken[] @@ -112,9 +114,12 @@ const App = () => { // Methods const isBuySupported = React.useCallback( (coinMarket: BraveWallet.CoinMarket) => { - return buyableAssets.some( - (asset) => - asset.symbol.toLowerCase() === coinMarket.symbol.toLowerCase() + return ( + buyableAssets?.some( + (asset) => + getAssetSymbol(asset).toLowerCase() === + coinMarket.symbol.toLowerCase() + ) ?? false ) }, [buyableAssets] diff --git a/components/brave_wallet_ui/page/router/unlocked_wallet_routes.tsx b/components/brave_wallet_ui/page/router/unlocked_wallet_routes.tsx index 09b8bc4b8db5..4b7cb20ef0cd 100644 --- a/components/brave_wallet_ui/page/router/unlocked_wallet_routes.tsx +++ b/components/brave_wallet_ui/page/router/unlocked_wallet_routes.tsx @@ -5,11 +5,20 @@ import * as React from 'react' import { Prompt, Route, Switch, useHistory } from 'react-router' +import { Location } from 'history' + +// Hooks +import { useLocalStorage } from '../../common/hooks/use_local_storage' + +// Constants +import { + LOCAL_STORAGE_KEYS // +} from '../../common/constants/local-storage-keys' -// constants +// Types import { WalletRoutes } from '../../constants/types' -// components +// Components import { CryptoView } from '../../components/desktop/views/crypto' import { WalletPageLayout } from '../../components/desktop/wallet-page-layout' import { @@ -19,33 +28,33 @@ import { BackupWalletRoutes // } from '../screens/backup-wallet/backup-wallet.routes' import { DepositFundsScreen } from '../screens/fund-wallet/deposit-funds' -import { FundWalletScreen } from '../screens/fund-wallet/fund_wallet_v1' +import { FundWalletScreen } from '../screens/fund-wallet/fund_wallet_v2' import { SimplePageWrapper } from '../screens/page-screen.styles' import { OnboardingSuccess // } from '../screens/onboarding/onboarding_success/onboarding_success' -import { PartnersConsentModal } from '../../components/desktop/popup-modals/partners_consent_modal/partners_consent_modal' -import { Location } from 'history' -import { useLocalStorage } from '../../common/hooks/use_local_storage' -import { LOCAL_STORAGE_KEYS } from '../../common/constants/local-storage-keys' +import { + PartnersConsentModal // +} from '../../components/desktop/popup-modals/partners_consent_modal/partners_consent_modal' export const UnlockedWalletRoutes = ({ sessionRoute }: { sessionRoute: WalletRoutes | undefined }) => { - // state + // State const [isModalOpen, setModalOpen] = React.useState(false) const [nextLocation, setNextLocation] = React.useState(null) const [shouldBlock, setShouldBlock] = React.useState(true) - // hooks + // Hooks const history = useHistory() const [acceptedTerms, setAcceptedTerms] = useLocalStorage( LOCAL_STORAGE_KEYS.HAS_ACCEPTED_PARTNER_TERMS, false ) + // Methods const handleAccept = () => { setAcceptedTerms(true) setModalOpen(false) diff --git a/components/brave_wallet_ui/page/screens/android-buy/android/fund-wallet.tsx b/components/brave_wallet_ui/page/screens/android-buy/android/fund-wallet.tsx index 56929177e9e9..8d8b7da0037e 100644 --- a/components/brave_wallet_ui/page/screens/android-buy/android/fund-wallet.tsx +++ b/components/brave_wallet_ui/page/screens/android-buy/android/fund-wallet.tsx @@ -27,7 +27,7 @@ import { // eslint-disable-next-line import/no-named-default default as BraveCoreThemeProvider } from '../../../../../common/BraveCoreThemeProvider' -import { FundWalletScreen } from '../../fund-wallet/fund-wallet' +import { FundWalletScreen } from '../../fund-wallet/fund_wallet_v2' // Resources import { setIconBasePath } from '@brave/leo/react/icon' diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.stories.tsx index 6b943126f235..61cdc3c7486a 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.stories.tsx @@ -4,11 +4,20 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' + +// Mock Data +import { + mockMeldFiatCurrency // +} from '../../../../../common/constants/mocks' + +// Components import { AmountButton } from './amount_button' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' -import { mockMeldFiatCurrency } from '../../../../../common/constants/mocks' +import { + WalletPageStory // +} from '../../../../../stories/wrappers/wallet-page-story-wrapper' export const _AmountButton = () => { + // State const [amount, setAmount] = React.useState('') return ( diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.style.ts index 9e0057c5fbaf..bbfc1f8ad7f4 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.style.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.style.ts @@ -4,11 +4,33 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import styled from 'styled-components' +import Icon from '@brave/leo/react/icon' import { color, font } from '@brave/leo/tokens/css/variables' + +// Shared Styles +import { Column } from '../../../../../components/shared/style' import { AmountInput as Input } from '../../../composer_ui/shared_composer.style' -import { layoutPanelWidth } from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' -import Icon from '@brave/leo/react/icon' -import Button from '@brave/leo/react/button' +import { + layoutPanelWidth // +} from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' + +export const Wrapper = styled(Column)` + align-items: flex-end; + @media (max-width: ${layoutPanelWidth}px) { + align-items: flex-start; + width: 100%; + } +` + +export const ButtonWrapper = styled(Column)` + align-items: flex-end; + @media (max-width: ${layoutPanelWidth}px) { + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + } +` export const CurrencyCode = styled.span` color: ${color.text.primary}; @@ -23,51 +45,19 @@ export const AmountInput = styled(Input).attrs({ })` color: ${color.text.primary}; font: ${font.heading.h1}; -` - -export const LabelWrapper = styled.div` - display: flex; - width: 100%; - gap: 8px; - justify-content: flex-end; - - @media (max-width: ${layoutPanelWidth}px) { - justify-content: flex-start; - } -` - -export const AmountWrapper = styled.div` - display: grid; - grid-template-columns: 1fr; - width: 100%; - align-items: center; - gap: 8px; - - @media (max-width: ${layoutPanelWidth}px) { - grid-template-columns: 2fr 1fr; - gap: 16px; - } + text-align: left; + width: 60px; ` export const AmountEstimate = styled.span` color: ${color.text.interactive}; font: ${font.default.semibold}; - @media (max-width: ${layoutPanelWidth}px) { font: ${font.default.regular}; } ` -export const FlipButton = styled(Button).attrs({ - kind: 'plain', - size: 'tiny' -})` - --leo-button-padding: 0; - flex: 0; -` - export const SwapVerticalIcon = styled(Icon).attrs({ name: 'swap-vertical' })` --leo-icon-color: ${color.icon.interactive}; --leo-icon-size: 20px; - cursor: pointer; ` diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.tsx index d325b3e10f22..1151aefb8e60 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/amount_button/amount_button.tsx @@ -5,22 +5,16 @@ import * as React from 'react' -// styles -import { Column, Row } from '../../../../../components/shared/style' -import { - CaretDown, - ControlsWrapper, - Label, - WrapperButton -} from '../shared/style' +// Styled Components +import { Row } from '../../../../../components/shared/style' +import { CaretDown, Label, WrapperButton } from '../shared/style' import { AmountEstimate, AmountInput, - AmountWrapper, CurrencyCode, - LabelWrapper, - FlipButton, - SwapVerticalIcon + SwapVerticalIcon, + Wrapper, + ButtonWrapper } from './amount_button.style' interface SelectAccountProps { @@ -30,7 +24,6 @@ interface SelectAccountProps { estimatedCryptoAmount?: string onChange: (amount: string) => void onClick: () => void - onFlipAmounts?: () => void } export const AmountButton = ({ @@ -39,10 +32,9 @@ export const AmountButton = ({ amount, estimatedCryptoAmount, onChange, - onClick, - onFlipAmounts + onClick }: SelectAccountProps) => { - // methods + // Methods const onInputChange = React.useCallback( (event: React.ChangeEvent) => { onChange(event.target.value) @@ -51,44 +43,43 @@ export const AmountButton = ({ ) return ( - - - - - - - - - - - - {currencyCode} - - - + + + + + + + + {currencyCode} + - {estimatedCryptoAmount ? ( - - {estimatedCryptoAmount} - - - - - ) : null} - - - - + + + {estimatedCryptoAmount ? ( + + {estimatedCryptoAmount} + + + ) : null} + + ) } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.stories.tsx index 862c81118bae..7d0dfcb16a26 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.stories.tsx @@ -3,12 +3,18 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' -import { BuyQuote } from './buy_quote' + +// Types +import { MeldCryptoQuote } from 'components/brave_wallet_ui/constants/types' + +// Mock Data import { mockMeldCryptoQuotes, mockServiceProviders } from '../../../../../common/constants/mocks' -import { MeldCryptoQuote } from 'components/brave_wallet_ui/constants/types' + +// Components +import { BuyQuote } from './buy_quote' export const _BuyQuote = () => { return ( diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.style.ts index be06ea34985f..ccbdce2bd7a9 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.style.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.style.ts @@ -4,11 +4,16 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import styled from 'styled-components' -import { color, font, spacing } from '@brave/leo/tokens/css/variables' import Icon from '@brave/leo/react/icon' import Button from '@brave/leo/react/button' import Label from '@brave/leo/react/label' -import { layoutPanelWidth } from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' +import { color, font, spacing } from '@brave/leo/tokens/css/variables' + +// Shared Styles +import { + layoutPanelWidth // +} from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' +import { Column } from '../../../../../components/shared/style' export const StyledWrapper = styled.div<{ isOpen?: boolean }>` display: flex; @@ -68,22 +73,18 @@ export const CaratIcon = styled(Icon)<{ margin-left: 8px; ` -export const QuoteDetailsWrapper = styled.div` - display: flex; - flex-direction: column; - width: 100%; - padding: ${spacing.xl} ${spacing['2Xl']}; - border-radius: 12px; - gap: 8px; - background-color: ${color.page.background}; +export const WrapperForPadding = styled(Column)` margin-top: ${spacing.xl}; + padding-left: 56px; + @media (max-width: ${layoutPanelWidth}px) { + padding-left: 0px; + } ` -export const QuoteDetailsRow = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; +export const QuoteDetailsWrapper = styled(Column)` + padding: ${spacing.xl} ${spacing['2Xl']}; + border-radius: 12px; + background-color: ${color.page.background}; ` export const QuoteDetailsLabel = styled.p` @@ -128,4 +129,5 @@ export const BestOptionLabel = styled(Label).attrs({ })` --leo-label-padding: 12px; color: ${color.text.primary}; + text-transform: capitalize; ` diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.tsx index 3c8aa709d9ec..f0bce46729ba 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/buy_quote/buy_quote.tsx @@ -6,17 +6,20 @@ import * as React from 'react' import Icon from '@brave/leo/react/icon' -// types +// Types import { + MeldCryptoCurrency, MeldCryptoQuote, MeldServiceProvider } from '../../../../../constants/types' -// utils +// Utils import Amount from '../../../../../utils/amount' import { toProperCase } from '../../../../../utils/string-utils' +import { getLocale } from '../../../../../../common/locale' +import { getAssetSymbol } from '../../../../../utils/meld_utils' -// styles +// Styled Components import { ProviderImage, ProviderName, @@ -26,21 +29,23 @@ import { PaymentMethodIcon, CaratIcon, QuoteDetailsWrapper, - QuoteDetailsRow, QuoteDetailsLabel, QuoteDetailsValue, Divider, QuoteTotal, BuyButton, - BestOptionLabel + BestOptionLabel, + WrapperForPadding } from './buy_quote.style' import { Column, Row } from '../../../../../components/shared/style' interface BuyQuoteProps { quote: MeldCryptoQuote serviceProviders: MeldServiceProvider[] + isOpenOverride?: boolean isBestOption?: boolean isCreatingWidget: boolean + selectedAsset?: MeldCryptoCurrency onBuy: (quote: MeldCryptoQuote) => void } @@ -49,6 +54,8 @@ export const BuyQuote = ({ serviceProviders, isBestOption, isCreatingWidget, + selectedAsset, + isOpenOverride, onBuy }: BuyQuoteProps) => { const { @@ -63,17 +70,22 @@ export const BuyQuote = ({ paymentMethod } = quote - // state - const [isOpen, setIsOpen] = React.useState(true) + // State + const [isOpen, setIsOpen] = React.useState(isOpenOverride ?? false) - // computed + // Computed const formattedSourceAmount = new Amount(sourceAmount ?? '').formatAsFiat( sourceCurrencyCode, 2 ) + + const assetsSymbol = selectedAsset + ? getAssetSymbol(selectedAsset) + : destinationCurrencyCode + const formattedCryptoAmount = new Amount( destinationAmount ?? '' - ).formatAsAsset(5, destinationCurrencyCode) + ).formatAsAsset(5, assetsSymbol) const formattedExchangeRate = new Amount(exchangeRate ?? '').formatAsFiat( '', @@ -100,7 +112,6 @@ export const BuyQuote = ({ setIsOpen(!isOpen)} > @@ -128,7 +139,7 @@ export const BuyQuote = ({
- BEST OPTION + {getLocale('braveWalletBestOption')} ) : null} @@ -148,39 +159,70 @@ export const BuyQuote = ({ width='100%' gap='8px' > - - - Exchange rate with fees - - ≈ {formattedExchangeRate} {sourceCurrencyCode} /{' '} - {destinationCurrencyCode} - - - - Price {sourceCurrencyCode} - - ≈ {amountWithoutFees} {sourceCurrencyCode} - - - - Fees - - {formattedTotalFee} {sourceCurrencyCode} - - - - - Total - - {formattedSourceAmount} {sourceCurrencyCode} - - - + + + + + {getLocale('braveWalletExchangeRateWithFees')} + + + ≈ {formattedExchangeRate} {sourceCurrencyCode} /{' '} + {assetsSymbol} + + + + + {getLocale('braveWalletPriceCurrency').replace( + '$1', + sourceCurrencyCode ?? '' + )} + + + ≈ {amountWithoutFees} {sourceCurrencyCode} + + + + + {getLocale('braveWalletFees')} + + + {formattedTotalFee} {sourceCurrencyCode} + + + + + + {getLocale('braveWalletConfirmTransactionTotal')} + + + {formattedSourceAmount} {sourceCurrencyCode} + + + + onBuy(quote)} > - Buy with {formattedProviderName} + {getLocale('braveWalletBuyWithProvider').replace( + '$1', + formattedProviderName + )}
diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/payment_method_filters/payment_method_filters.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/payment_method_filters/payment_method_filters.stories.tsx deleted file mode 100644 index ae8bec68f73a..000000000000 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/payment_method_filters/payment_method_filters.stories.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2024 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -import * as React from 'react' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' -import { PaymentMethodFilters } from './payment_method_filters' -import { - mockMeldCountries, - mockMeldPaymentMethods -} from '../../../../../common/constants/mocks' - -export const _PaymentMethodFilters = () => { - const [selectedCountryCode, setSelectedCountryCode] = React.useState('AS') - return ( - - - setSelectedCountryCode(countryCode) - } - isLoading={false} - paymentMethods={mockMeldPaymentMethods} - onSelectPaymentMethods={(paymentMethods) => {}} - /> - - ) -} - -export default { - component: _PaymentMethodFilters, - title: 'Payment Method Filters' -} diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/payment_method_filters/payment_method_filters.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/payment_method_filters/payment_method_filters.tsx deleted file mode 100644 index 3277f95d1338..000000000000 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/payment_method_filters/payment_method_filters.tsx +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2024 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -import * as React from 'react' -import { DialogProps } from '@brave/leo/react/dialog' -import * as leo from '@brave/leo/tokens/css/variables' -import Checkbox from '@brave/leo/react/checkbox' - -// types -import { MeldCountry, MeldPaymentMethod } from '../../../../../constants/types' - -// utils -import { isComponentInStorybook } from '../../../../../utils/string-utils' - -// styles -import { Dialog, Dropdown, DialogTitle } from '../shared/style' -import { Column, Row } from '../../../../../components/shared/style' - -interface Props extends DialogProps { - isLoading: boolean - selectedCountryCode: string - countries: MeldCountry[] - paymentMethods: MeldPaymentMethod[] - onSelectPaymentMethods: (paymentMethods: MeldPaymentMethod[]) => void - onSelectCountry: (countryCode: string) => void -} - -const PaymentMethodItem = ({ - isChecked, - paymentMethod, - onSelect -}: { - isChecked: boolean - paymentMethod: MeldPaymentMethod - onSelect: (paymentMethod: MeldPaymentMethod) => void -}) => { - const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches - const logoUrl = isDarkMode - ? paymentMethod.logoImages?.darkUrl - : paymentMethod.logoImages?.lightUrl - - const isStorybook = isComponentInStorybook() - - return ( - - onSelect(paymentMethod)} - > - - - - {paymentMethod.name} - - - - ) -} - -export const PaymentMethodFilters = (props: Props) => { - const { - isLoading, - selectedCountryCode, - countries, - paymentMethods, - onSelectCountry, - onSelectPaymentMethods, - ...rest - } = props - - // state - const [selectedPaymentMethods, setSelectedPaymentMethods] = React.useState< - MeldPaymentMethod[] - >(paymentMethods || []) - - // computed - const selectedCountry = countries.find( - (country) => - country.countryCode.toLowerCase() === selectedCountryCode.toLowerCase() - ) - - // methods - const isPaymentMethodSelected = (paymentMethod: MeldPaymentMethod) => - selectedPaymentMethods.findIndex( - (selectedPaymentMethod) => - selectedPaymentMethod.paymentMethod === paymentMethod.paymentMethod - ) > -1 - - const onSelectPaymentMethod = (paymentMethod: MeldPaymentMethod) => { - const newSelectedPaymentMethods = isPaymentMethodSelected(paymentMethod) - ? selectedPaymentMethods.filter( - (selectedPaymentMethod) => - selectedPaymentMethod.paymentMethod !== paymentMethod.paymentMethod - ) - : [...selectedPaymentMethods, paymentMethod] - setSelectedPaymentMethods(newSelectedPaymentMethods) - onSelectPaymentMethods(newSelectedPaymentMethods) - } - - return ( - - Filters - onSelectCountry(detail.value as string)} - > -
Countries
-
{selectedCountry?.name}
- {countries.map((country) => { - return ( - - {country.name} - - ) - })} -
- - - {paymentMethods.map((paymentMethod) => ( - - ))} - -
- ) -} diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.stories.tsx index 7b83cb7fe35a..46056872fd4f 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.stories.tsx @@ -4,15 +4,24 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' + +// Mock Data +import { + mockAccounts // +} from '../../../../../stories/mock-data/mock-wallet-accounts' + +// Components import { SelectAccount } from './select_account' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' -import { mockAccounts } from '../../../../../stories/mock-data/mock-wallet-accounts' +import { + WalletPageStory // +} from '../../../../../stories/wrappers/wallet-page-story-wrapper' export const _SelectAccount = () => { return ( alert('Close was clicked.')} accounts={mockAccounts} onSelect={(account) => console.log(account)} /> diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.tsx index de8ccf7b2193..de856ec1507b 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account/select_account.tsx @@ -3,30 +3,55 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. -import { DialogProps } from '@brave/leo/react/dialog' import * as React from 'react' +import { DialogProps } from '@brave/leo/react/dialog' import { spacing } from '@brave/leo/tokens/css/variables' -// types -import { BraveWallet } from '../../../../../constants/types' +// Selectors +import { + useSafeUISelector // +} from '../../../../../common/hooks/use-safe-selector' +import { UISelectors } from '../../../../../common/selectors' + +// Types +import { BraveWallet, MeldCryptoCurrency } from '../../../../../constants/types' -// utils +// Utils import { reduceAddress } from '../../../../../utils/reduce-address' +import { getLocale } from '../../../../../../common/locale' -// components -import { CreateAccountIcon } from '../../../../../components/shared/create-account-icon/create-account-icon' +// Components +import { + CreateAccountIcon // +} from '../../../../../components/shared/create-account-icon/create-account-icon' +import { + BottomSheet // +} from '../../../../../components/shared/bottom_sheet/bottom_sheet' -// styles +// Styled Components import { ContainerButton, Dialog, DialogTitle } from '../shared/style' import { Column, - Row + Row, + ScrollableColumn } from '../../../../../components/shared/style' import { AccountAddress, AccountName } from './select_account.style' +import { getMeldTokensCoinType } from '../../../../../utils/meld_utils' + +const testnetAccountKeyringIds = [ + BraveWallet.KeyringId.kBitcoin84Testnet, + BraveWallet.KeyringId.kBitcoinHardwareTestnet, + BraveWallet.KeyringId.kBitcoinImportTestnet, + BraveWallet.KeyringId.kFilecoinTestnet, + BraveWallet.KeyringId.kZCashTestnet +] interface SelectAccountProps extends DialogProps { accounts: BraveWallet.AccountInfo[] selectedAccount?: BraveWallet.AccountInfo + selectedAsset?: MeldCryptoCurrency + isOpen: boolean + onClose: () => void onSelect: (account: BraveWallet.AccountInfo) => void } @@ -37,9 +62,7 @@ interface AccountProps { export const Account = ({ account, onSelect }: AccountProps) => { return ( - onSelect(account)} - > + onSelect(account)}> { } export const SelectAccount = (props: SelectAccountProps) => { - const { accounts, ...rest } = props + const { accounts, selectedAsset, onSelect, isOpen, onClose, ...rest } = props + + // Selectors + const isPanel = useSafeUISelector(UISelectors.isPanel) + + // Memos + const accountByCoinType = React.useMemo(() => { + if (selectedAsset) { + return accounts.filter( + (account) => + account.accountId.coin === getMeldTokensCoinType(selectedAsset) && + !testnetAccountKeyringIds.includes(account.accountId.keyringId) + ) + } + return accounts + }, [selectedAsset, accounts]) + + const selectAccountContent = React.useMemo(() => { + return ( + <> + + {getLocale('braveWalletSelectAccount')} + + + + {accountByCoinType.map((account) => ( + + ))} + + + ) + }, [accountByCoinType, onSelect, isPanel]) + + if (isPanel) { + return ( + + + {selectAccountContent} + + + ) + } return ( - Select Account - - - {accounts.map((account) => ( - - ))} - + {selectAccountContent} ) } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.stories.tsx index c6f06b05a725..e3bc3919eabe 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.stories.tsx @@ -4,10 +4,16 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' -import { SelectAccountButton } from './select_account_button' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' + +// Mock Data import { mockAccount } from '../../../../../common/constants/mocks' +// Components +import { SelectAccountButton } from './select_account_button' +import { + WalletPageStory // +} from '../../../../../stories/wrappers/wallet-page-story-wrapper' + export const _SelectAccountButton = () => { return ( diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.style.ts index f2ecde6c7d0f..ed3cc62f6c7f 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.style.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.style.ts @@ -5,7 +5,11 @@ import styled from 'styled-components' import { color, font, spacing } from '@brave/leo/tokens/css/variables' -import { layoutPanelWidth } from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' + +// Shared Styles +import { + layoutPanelWidth // +} from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' export const AccountName = styled.h3` color: ${color.text.primary}; @@ -28,7 +32,6 @@ export const AccountAddress = styled.span` color: ${color.text.secondary}; font: ${font.xSmall.semibold}; line-height: 16px; - @media (max-width: ${layoutPanelWidth}) { background-color: transparent; } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.tsx index cf831482bdf4..668535d778d9 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_account_button/select_account_button.tsx @@ -5,21 +5,22 @@ import * as React from 'react' -// types +// Types import { BraveWallet } from '../../../../../constants/types' -// components -import { CreateAccountIcon } from '../../../../../components/shared/create-account-icon/create-account-icon' +// Hooks +import { + useReceiveAddressQuery // +} from '../../../../../common/slices/api.slice.extra' -// styles -import { Column, Row } from '../../../../../components/shared/style' +// Components import { - CaretDown, - ControlsWrapper, - ControlText, - Label, - WrapperButton -} from '../shared/style' + CreateAccountIcon // +} from '../../../../../components/shared/create-account-icon/create-account-icon' + +// Styled Components +import { Column, Row } from '../../../../../components/shared/style' +import { CaretDown, ControlText, Label, WrapperButton } from '../shared/style' import { AccountAddress } from './select_account_button.style' interface SelectAccountProps { @@ -33,28 +34,29 @@ export const SelectAccountButton = ({ selectedAccount, onClick }: SelectAccountProps) => { + const { receiveAddress } = useReceiveAddressQuery(selectedAccount?.accountId) + return ( - - - - {selectedAccount?.name} - + + + {selectedAccount?.name} - + - {selectedAccount?.address} + {receiveAddress} ) } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.stories.tsx index 1494d6e8cb81..ae4cbc6a5a82 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.stories.tsx @@ -4,22 +4,33 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' -import { SelectAsset } from './select_asset' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' + +// Mock Data import { mockMeldCryptoCurrencies, mockMeldFiatCurrencies } from '../../../../../common/constants/mocks' + +// Types import { MeldCryptoCurrency } from '../../../../../constants/types' +// Components +import { SelectAsset } from './select_asset' +import { + WalletPageStory // +} from '../../../../../stories/wrappers/wallet-page-story-wrapper' + export const _SelectAsset = () => { + // State const [selectedCurrency, setSelectedCurrency] = React.useState< MeldCryptoCurrency | undefined >(undefined) + return ( alert('Close was clicked.')} assets={mockMeldCryptoCurrencies} isLoadingAssets={false} isLoadingSpotPrices={false} diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.style.ts index 439cf75a5ef5..301ebf8b528a 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.style.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.style.ts @@ -7,6 +7,9 @@ import styled from 'styled-components' import { font, color } from '@brave/leo/tokens/css/variables' import ProgressRing from '@brave/leo/react/progressRing' +// Shared Styles +import { Row } from '../../../../../components/shared/style' + export const Wrapper = styled.div` position: fixed; top: 0; @@ -67,3 +70,8 @@ export const AutoSizerStyle = { width: '100%', flex: 1 } + +export const SearchAndNetworkFilterRow = styled(Row)` + background-color: ${color.container.highlight}; + border-radius: 8px; +` diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.tsx index 5d8174ece2f8..7a673cd5f676 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset/select_asset.tsx @@ -7,9 +7,24 @@ import * as React from 'react' import { VariableSizeList as List } from 'react-window' import AutoSizer from 'react-virtualized-auto-sizer' import { DialogProps } from '@brave/leo/react/dialog' -import Icon from '@brave/leo/react/icon' -// types +// Selectors +import { + useSafeUISelector // +} from '../../../../../common/hooks/use-safe-selector' +import { UISelectors } from '../../../../../common/selectors' + +// Options +import { + AllNetworksOption // +} from '../../../../../options/network-filter-options' + +// Queries +import { + useGetAllKnownNetworksQuery // +} from '../../../../../common/slices/api.slice' + +// Types import { BraveWallet, MeldCryptoCurrency, @@ -17,11 +32,31 @@ import { SpotPriceRegistry } from '../../../../../constants/types' -// utils +// Utils +import { + getAssetSymbol, + getTokenPriceFromRegistry, + getAssetIdKey, + getMeldTokensCoinType +} from '../../../../../utils/meld_utils' import { getLocale } from '../../../../../../common/locale' import Amount from '../../../../../utils/amount' -// styles +// Components +import { + CreateNetworkIcon // +} from '../../../../../components/shared/create-network-icon' +import { + NetworkFilterSelector // +} from '../../../../../components/desktop/network-filter-selector' +import { + SearchBar // +} from '../../../../../components/shared/search-bar/index' +import { + BottomSheet // +} from '../../../../../components/shared/bottom_sheet/bottom_sheet' + +// Styled Components import { Column, Row } from '../../../../../components/shared/style' import { AssetImage, @@ -29,20 +64,17 @@ import { AssetNetwork, AssetPrice, Loader, - AutoSizerStyle + AutoSizerStyle, + SearchAndNetworkFilterRow } from './select_asset.style' import { ContainerButton, Dialog, DialogTitle, ListTitle, - SearchInput + IconsWrapper, + NetworkIconWrapper } from '../shared/style' -import { - getAssetSymbol, - getTokenPriceFromRegistry, - getAssetIdKey -} from '../../../../../utils/meld_utils' interface SelectAssetProps extends DialogProps { assets: MeldCryptoCurrency[] @@ -51,6 +83,8 @@ interface SelectAssetProps extends DialogProps { selectedAsset?: MeldCryptoCurrency selectedFiatCurrency?: MeldFiatCurrency spotPriceRegistry?: SpotPriceRegistry + isOpen: boolean + onClose: () => void onSelectAsset: (asset: MeldCryptoCurrency) => void } @@ -58,12 +92,12 @@ interface AssetListItemProps { index: number style: React.CSSProperties setSize: (index: number, size: number) => void - asset: MeldCryptoCurrency isLoadingPrices: boolean assetPrice?: BraveWallet.AssetPrice fiatCurrencyCode?: string onSelect: (currency: MeldCryptoCurrency) => void + network?: BraveWallet.NetworkInfo } const assetItemHeight = 64 @@ -76,10 +110,12 @@ export const AssetListItem = ({ isLoadingPrices, assetPrice, fiatCurrencyCode, - onSelect + onSelect, + network }: AssetListItemProps) => { const { symbolImageUrl, currencyCode, chainName } = asset + // Computed const assetSymbol = getAssetSymbol(asset) const networkDescription = currencyCode !== '' @@ -87,8 +123,11 @@ export const AssetListItem = ({ .replace('$1', assetSymbol ?? '') .replace('$2', chainName ?? '') : chainName + const formattedPrice = assetPrice + ? new Amount(assetPrice.price).formatAsFiat(fiatCurrencyCode ?? '', 4) + : '' - // methods + // Methods const handleSetSize = React.useCallback( (ref: HTMLButtonElement | null) => { if (ref) { @@ -98,10 +137,6 @@ export const AssetListItem = ({ [index, setSize] ) - const formattedPrice = assetPrice - ? new Amount(assetPrice.price).formatAsFiat(fiatCurrencyCode ?? '', 4) - : '' - return (
- + + + + + + {asset.name} {networkDescription} @@ -142,32 +186,65 @@ export const SelectAsset = (props: SelectAssetProps) => { selectedFiatCurrency, spotPriceRegistry, onSelectAsset, + isOpen, + onClose, ...rest } = props - // state + // Selectors + const isPanel = useSafeUISelector(UISelectors.isPanel) + + // State const [searchText, setSearchText] = React.useState('') + const [selectedNetworkFilter, setSelectedNetworkFilter] = + React.useState(AllNetworksOption) - // refs + // Refs const listRef = React.useRef(null) const itemSizes = React.useRef( new Array(assets.length).fill(assetItemHeight) ) - // memos + // Queries + const { data: networkList = [] } = useGetAllKnownNetworksQuery() + + // Memos + const assetsFilteredByNetwork = React.useMemo(() => { + if (selectedNetworkFilter.chainId === AllNetworksOption.chainId) { + return assets + } + return assets.filter( + (asset) => asset.chainId === selectedNetworkFilter.chainId + ) + }, [selectedNetworkFilter, assets]) + const searchResults = React.useMemo(() => { - if (searchText === '') return assets + if (searchText === '') return assetsFilteredByNetwork - return assets.filter((asset) => { + return assetsFilteredByNetwork.filter((asset) => { + const assetSymbol = getAssetSymbol(asset).toLowerCase() + const assetName = asset.name?.toLowerCase() ?? '' return ( - asset?.name?.toLowerCase().includes(searchText.toLowerCase()) || - asset.currencyCode.toLowerCase().includes(searchText.toLowerCase()) || - asset?.contractAddress?.toLowerCase().includes(searchText.toLowerCase()) + assetName.startsWith(searchText.toLowerCase()) || + assetSymbol.startsWith(searchText.toLowerCase()) || + asset?.contractAddress + ?.toLowerCase() + .startsWith(searchText.toLowerCase()) || + assetName.includes(searchText.toLowerCase()) || + assetSymbol.includes(searchText.toLowerCase()) ) }) - }, [assets, searchText]) + }, [assetsFilteredByNetwork, searchText]) + + const networks = React.useMemo(() => { + const allChainIds = assets.map((asset) => asset.chainId) + let reducedChainIds = [...new Set(allChainIds)] + return networkList.filter((network) => + reducedChainIds.includes(network.chainId) + ) + }, [assets, networkList]) - // methods + // Methods const getListItemKey = (index: number, assets: MeldCryptoCurrency[]) => { return getAssetIdKey(assets[index]) } @@ -188,91 +265,165 @@ export const SelectAsset = (props: SelectAssetProps) => { } }, []) + const getAssetsNetwork = React.useCallback( + (asset: MeldCryptoCurrency) => { + return networkList.find( + (network) => + network.chainId.toLowerCase() === asset.chainId?.toLowerCase() && + getMeldTokensCoinType(asset) === network.coin + ) + }, + [networkList] + ) + + const updateSearchValue = React.useCallback( + (event: React.ChangeEvent) => { + setSearchText(event.target.value) + }, + [] + ) + + const onSelectNetwork = React.useCallback( + (network: BraveWallet.NetworkInfo) => { + setSelectedNetworkFilter(network) + }, + [] + ) + + // Memos + const selectAssetContent = React.useMemo(() => { + return ( + <> + + {getLocale('braveWalletSelectAsset')} + + + + + + + {isLoadingAssets && ( + + + + )} + {searchResults.length === 0 && !isLoadingAssets ? ( + + {getLocale('braveWalletNoAvailableAssets')} + + ) : ( + <> + + {getLocale('braveWalletAsset')} + ~ {getLocale('braveWalletPrice')} + + + {function ({ + width, + height + }: { + width: number + height: number + }) { + return ( + ( + + )} + /> + ) + }} + + + )} + + + ) + }, [ + getAssetsNetwork, + getSize, + isLoadingAssets, + isLoadingSpotPrices, + networks, + onSelectAsset, + onSelectNetwork, + searchResults, + searchText, + selectedFiatCurrency, + selectedNetworkFilter, + setSize, + spotPriceRegistry, + updateSearchValue + ]) + + if (isPanel) { + return ( + + + {selectAssetContent} + + + ) + } + return ( - Select Asset - - setSearchText(e.value)} - > - - - - - {isLoadingAssets && ( - - - - )} - {searchResults.length === 0 && !isLoadingAssets ? ( - - No available assets - - ) : ( - <> - - Asset - ~ Price - - - {function ({ width, height }: { width: number; height: number }) { - return ( - ( - - )} - /> - ) - }} - - - )} - + {selectAssetContent} ) } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.stories.tsx index c450766dd0da..512313af03af 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.stories.tsx @@ -4,9 +4,17 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' + +// Mock Data +import { + mockMeldCryptoCurrencies // +} from '../../../../../common/constants/mocks' + +// Components import { SelectAssetButton } from './select_asset_button' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' -import { mockMeldCryptoCurrencies } from '../../../../../common/constants/mocks' +import { + WalletPageStory // +} from '../../../../../stories/wrappers/wallet-page-story-wrapper' export const _SelectAssetButton = () => { return ( diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.tsx index 1f0963f5bf6b..29e8bcf76ff0 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_asset_button/select_asset_button.tsx @@ -4,21 +4,38 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' +import { skipToken } from '@reduxjs/toolkit/query' -// types +// Queries +import { + useGetNetworkQuery // +} from '../../../../../common/slices/api.slice' + +// Types import { MeldCryptoCurrency } from '../../../../../constants/types' -// styles +// Utils +import { + getAssetSymbol, + getMeldTokensCoinType +} from '../../../../../utils/meld_utils' + +// Components +import { + CreateNetworkIcon // +} from '../../../../../components/shared/create-network-icon' + +// Styled Components import { Column, Row } from '../../../../../components/shared/style' import { AssetIcon, CaretDown, - ControlsWrapper, ControlText, Label, - WrapperButton + WrapperButton, + IconsWrapper, + NetworkIconWrapper } from '../shared/style' -import { getAssetSymbol } from '../../../../../utils/meld_utils' interface SelectAssetButtonProps { labelText: string @@ -29,6 +46,17 @@ interface SelectAssetButtonProps { export const SelectAssetButton = (props: SelectAssetButtonProps) => { const { labelText, selectedAsset, onClick } = props + // Queries + const { data: tokensNetwork } = useGetNetworkQuery( + selectedAsset?.chainId + ? { + chainId: selectedAsset.chainId, + coin: getMeldTokensCoinType(selectedAsset) + } + : skipToken + ) + + // Computed const assetSymbol = selectedAsset ? getAssetSymbol(selectedAsset) : '' return ( @@ -36,25 +64,34 @@ export const SelectAssetButton = (props: SelectAssetButtonProps) => { - - - {selectedAsset && ( - <> + + {selectedAsset && ( + <> + - {assetSymbol} - - )} - + {tokensNetwork && ( + + + + )} + + {assetSymbol} + + )} - + diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.stories.tsx index 64d0ad5bf8df..fec7e32796eb 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.stories.tsx @@ -4,19 +4,32 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' -import { SelectCurrency } from './select_currency' -import WalletPageStory from '../../../../../stories/wrappers/wallet-page-story-wrapper' -import { mockMeldFiatCurrencies } from '../../../../../common/constants/mocks' + +// Mock Data +import { + mockMeldFiatCurrencies // +} from '../../../../../common/constants/mocks' + +// Types import { MeldFiatCurrency } from '../../../../../constants/types' +// Components +import { SelectCurrency } from './select_currency' +import { + WalletPageStory // +} from '../../../../../stories/wrappers/wallet-page-story-wrapper' + export const _SelectCurrency = () => { + // State const [selectedCurrency, setSelectedCurrency] = React.useState< MeldFiatCurrency | undefined >(undefined) + return ( alert('Close was clicked.')} currencies={mockMeldFiatCurrencies} selectedCurrency={selectedCurrency} onSelectCurrency={(currency) => setSelectedCurrency(currency)} @@ -28,4 +41,4 @@ export const _SelectCurrency = () => { export default { component: _SelectCurrency, title: 'Fund Wallet - Select Currency' -} +} diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.style.ts index 929388281b66..31775ef19de6 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.style.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.style.ts @@ -4,10 +4,14 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import styled from 'styled-components' -import { font, color } from '@brave/leo/tokens/css/variables' -import { layoutPanelWidth } from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' import Input from '@brave/leo/react/input' import Label from '@brave/leo/react/label' +import { font, color } from '@brave/leo/tokens/css/variables' + +// Shared Styles +import { + layoutPanelWidth // +} from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' export const SearchInput = styled(Input).attrs({ mode: 'filled', @@ -16,7 +20,6 @@ export const SearchInput = styled(Input).attrs({ margin-top: 2px; width: 100%; padding-bottom: 8px; - @media (max-width: ${layoutPanelWidth}px) { size: small; } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.tsx index 9eebf993a7e2..e6d1b65d3111 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/select_currency/select_currency.tsx @@ -7,23 +7,43 @@ import * as React from 'react' import { DialogProps } from '@brave/leo/react/dialog' import Icon from '@brave/leo/react/icon' -// types +// Selectors +import { + useSafeUISelector // +} from '../../../../../common/hooks/use-safe-selector' +import { UISelectors } from '../../../../../common/selectors' + +// Types import { MeldFiatCurrency } from '../../../../../constants/types' -// styles -import { Column, Row } from '../../../../../components/shared/style' +// Utils +import { getLocale } from '../../../../../../common/locale' + +// Components +import { + BottomSheet // +} from '../../../../../components/shared/bottom_sheet/bottom_sheet' + +// Styled Components +import { + Column, + Row, + ScrollableColumn +} from '../../../../../components/shared/style' import { CurrencyImage, CurrencyName, CurrencyCode, SearchInput, - SelectedLabel, + SelectedLabel } from './select_currency.style' import { ContainerButton, Dialog, DialogTitle } from '../shared/style' interface SelectCurrencyProps extends DialogProps { currencies: MeldFiatCurrency[] selectedCurrency?: MeldFiatCurrency + isOpen: boolean + onClose: () => void onSelectCurrency: (currency: MeldFiatCurrency) => void } @@ -49,7 +69,9 @@ export const CurrencyListItem = ({ justifyContent='flex-end' gap='16px' > - {isSelected && Selected} + {isSelected && ( + {getLocale('braveWalletSelected')} + )} {currency.currencyCode} @@ -57,12 +79,22 @@ export const CurrencyListItem = ({ } export const SelectCurrency = (props: SelectCurrencyProps) => { - const { currencies, selectedCurrency, onSelectCurrency, ...rest } = props + const { + currencies, + selectedCurrency, + onSelectCurrency, + isOpen, + onClose, + ...rest + } = props - // state + // Selectors + const isPanel = useSafeUISelector(UISelectors.isPanel) + + // State const [searchText, setSearchText] = React.useState('') - // memos + // Memos const searchResults = React.useMemo(() => { if (searchText === '') return currencies @@ -74,52 +106,83 @@ export const SelectCurrency = (props: SelectCurrencyProps) => { }) }, [currencies, searchText]) + const selectCurrencyContent = React.useMemo(() => { + return ( + <> + + {getLocale('braveWalletSelectCurrency')} + + + setSearchText(e.value)} + > + + + + + {searchResults.length === 0 ? ( + + {getLocale('braveWalletNoAvailableCurrencies')} + + ) : ( + + {searchResults.map((currency) => ( + + ))} + + )} + + + ) + }, [onSelectCurrency, searchResults, selectedCurrency]) + + if (isPanel) { + return ( + + + {selectCurrencyContent} + + + ) + } + return ( - Select Currency - - setSearchText(e.value)} - > - - - - - {searchResults.length === 0 ? ( - - No available currencies - - ) : ( - searchResults.map((currency) => ( - - )) - )} - + {selectCurrencyContent} ) } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/components/shared/style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/components/shared/style.ts index 0ba64dfc2d91..5ee58c9dd4a1 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/components/shared/style.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/components/shared/style.ts @@ -9,10 +9,12 @@ import Icon from '@brave/leo/react/icon' import LeoDialog from '@brave/leo/react/dialog' import LeoDropdown from '@brave/leo/react/dropdown' import Input from '@brave/leo/react/input' -import Button from '@brave/leo/react/button' +// Shared Styles import { WalletButton } from '../../../../../components/shared/style' -import { layoutPanelWidth } from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' +import { + layoutPanelWidth // +} from '../../../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' export const Label = styled.label` color: ${color.text.tertiary}; @@ -44,30 +46,15 @@ export const WrapperButton = styled(WalletButton)` background-color: transparent; border: none; cursor: pointer; - - @media (max-width: ${layoutPanelWidth}px) { - width: 100%; - justify-content: space-between; - } -` - -export const ControlsWrapper = styled.div` - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - gap: 8px; - padding: 8px 0; - + padding: 0px; @media (max-width: ${layoutPanelWidth}px) { - width: 100%; justify-content: space-between; } ` export const ControlText = styled.h3` color: ${color.text.primary}; - text-align: center; + text-align: left; font: ${font.heading.h3}; padding: 0; margin: 0; @@ -79,7 +66,6 @@ export const Dialog = styled(LeoDialog).attrs({ --leo-dialog-backdrop-background: rgba(17, 18, 23, 0.35); --leo-dialog-backdrop-filter: blur(8px); --leo-dialog-padding: 16px; - .subtitle { border: 1px solid red; margin-bottom: 0; @@ -91,7 +77,6 @@ export const DialogTitle = styled.p` color: ${color.text.primary}; text-align: left; margin: 0; - @media (max-width: ${layoutPanelWidth}px) { font: ${font.heading.h3}; } @@ -109,14 +94,9 @@ export const ContainerButton = styled(WalletButton)` outline: ${color.primary[70]}; border-radius: 8px; cursor: pointer; - &:hover { background: ${color.container.interactive}; } - - @media (max-width: ${layoutPanelWidth}px) { - max-width: 344px; - } ` export const ListTitle = styled.span` @@ -133,24 +113,26 @@ export const SearchInput = styled(Input).attrs({ size: window.innerWidth <= layoutPanelWidth ? 'small' : 'normal' })` width: 100%; - - @media (max-width: ${layoutPanelWidth}px) { - size: small; - } ` -export const FilterButton = styled(Button).attrs({ - kind: 'outline' -})` - --leo-button-color: ${color.icon.interactive}; - --leo-button-border-color: ${color.icon.interactive}; - flex-grow: 0; - width: 36px; +export const IconsWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; + position: relative; ` -export const FilterIcon = styled(Icon).attrs({ - name: 'filter-settings' -})` - --leo-icon-size: 18px; - --leo-icon-color: ${color.icon.interactive}; +export const NetworkIconWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-direction: row; + position: absolute; + bottom: -3px; + right: -3px; + background-color: ${color.container.background}; + border-radius: 100%; + padding: 2px; + z-index: 3; ` diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/fund-wallet.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/fund-wallet.tsx index 2733854bddf1..fd655f588aac 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/fund-wallet.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/fund-wallet.tsx @@ -95,10 +95,7 @@ import { NetworkFilterSelector // } from '../../../components/desktop/network-filter-selector' import { BuyOptions } from '../../../options/buy-with-options' -import { - makeFundWalletPurchaseOptionsRoute, - makeFundWalletRoute -} from '../../../utils/routes-utils' +import { makeFundWalletPurchaseOptionsRoute } from '../../../utils/routes-utils' import { networkSupportsAccount } from '../../../utils/network-utils' interface Props { @@ -207,12 +204,11 @@ function AssetSelection({ isAndroid }: Props) { selectedCurrency={selectedCurrency} key={assetId} token={asset} - onClick={() => history.push(makeFundWalletRoute(assetId))} ref={ref} /> ) }, - [history, selectedCurrency] + [selectedCurrency] ) // memos & computed @@ -391,39 +387,6 @@ function AssetSelection({ isAndroid }: Props) { return } - const searchValueLower = searchValue.toLowerCase() - - // save latest form values in router history - history.replace( - makeFundWalletRoute(selectedOnRampAssetId, { - currencyCode: selectedCurrency, - buyAmount, - // save latest search-box value (if it matches selection name - // or symbol) - searchText: - searchValue && - (selectedAsset?.name - .toLowerCase() - .startsWith(searchValueLower) || - selectedAsset?.symbol - .toLowerCase() - .startsWith(searchValueLower)) - ? searchValue - : undefined, - // saving network filter (if it matches selection) - chainId: - selectedAsset?.chainId === selectedNetworkFilter.chainId - ? selectedNetworkFilter.chainId || - AllNetworksOption.chainId - : AllNetworksOption.chainId, - coinType: - selectedAsset?.coin === selectedNetworkFilter.coin - ? selectedNetworkFilter.coin.toString() || - AllNetworksOption.coin.toString() - : AllNetworksOption.coin.toString() - }) - ) - // go to payment option selection history.push( makeFundWalletPurchaseOptionsRoute(selectedOnRampAssetId, { diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.style.ts deleted file mode 100644 index 7de27e3f5bf4..000000000000 --- a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.style.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2024 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -import styled from 'styled-components' -import { color, font, spacing } from '@brave/leo/tokens/css/variables' -import ProgressRing from '@brave/leo/react/progressRing' -import { layoutPanelWidth } from '../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' -import { Column, ScrollableColumn } from '../../../components/shared/style' - -export const ContentWrapper = styled(Column)` - @media screen and (max-width: ${layoutPanelWidth}px) { - margin: 8px; - } -` - -export const ControlPanel = styled.div` - display: grid; - grid-template-columns: 160px auto auto; - gap: 16px; - width: 100%; - align-items: flex-start; - - @media screen and (max-width: ${layoutPanelWidth}px) { - grid-template-columns: 1fr; - border-radius: 16px; - padding: 8px; - background-color: ${color.container.background}; - } -` - -export const ServiceProvidersWrapper = styled(ScrollableColumn)` - display: flex; - flex-wrap: wrap; - gap: ${spacing['3Xl']}; - - @media screen and (max-width: ${layoutPanelWidth}px) { - border-radius: 16px; - background-color: ${color.container.background}; - } -` - -export const LoadingWrapper = styled(Column)` - justify-content: center; - align-items: center; - width: 100%; - height: 300px; -` - -export const LoaderText = styled.p` - color: ${color.text.primary}; - font: ${font.default.regular}; - text-align: center; -` - -export const Loader = styled(ProgressRing).attrs({ - mode: 'indeterminate' -})` - --leo-progressring-size: 32px; -` - -export const Divider = styled.div` - width: 100%; - height: 1px; - background-color: ${color.divider.subtle}; - margin-top: ${spacing['3Xl']}; - - @media screen and (max-width: ${layoutPanelWidth}px) { - display: none; - } -` diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.tsx deleted file mode 100644 index f12dc467ab02..000000000000 --- a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.tsx +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) 2024 The Brave Authors. All rights reserved. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this file, -// You can obtain one at https://mozilla.org/MPL/2.0/. - -import * as React from 'react' -import Icon from '@brave/leo/react/icon' - -// hooks -import { useBuy } from '../swap/hooks/useBuy' - -// utils -import { getLocale } from '../../../../common/locale' -import { getAssetSymbol } from '../../../utils/meld_utils' - -// selectors -import { useSafeUISelector } from '../../../common/hooks/use-safe-selector' -import { UISelectors } from '../../../common/selectors' - -// components -import WalletPageWrapper from '../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper' -import { PageTitleHeader } from '../../../components/desktop/card-headers/page-title-header' -import { SelectAssetButton } from './components/select_asset_button/select_asset_button' -import { SelectAccountButton } from './components/select_account_button/select_account_button' -import { AmountButton } from './components/amount_button/amount_button' -import { SelectCurrency } from './components/select_currency/select_currency' -import { SelectAccount } from './components/select_account/select_account' -import { SelectAsset } from './components/select_asset/select_asset' -import { BuyQuote } from './components/buy_quote/buy_quote' - -// styles -import { - ContentWrapper, - ControlPanel, - Divider, - Loader, - LoaderText, - LoadingWrapper, - ServiceProvidersWrapper -} from './fund_wallet_v1.style' -import { Column, Row } from '../../../components/shared/style' -import { PaymentMethodFilters } from './components/payment_method_filters/payment_method_filters' -import { - SearchInput, - FilterButton, - FilterIcon -} from './components/shared/style' - -export const FundWalletScreen = () => { - // state - const [isCurrencyDialogOpen, setIsCurrencyDialogOpen] = React.useState(false) - const [isAssetDialogOpen, setIsAssetDialogOpen] = React.useState(false) - const [isAccountDialogOpen, setIsAccountDialogOpen] = React.useState(false) - const [isPaymentFiltersOpen, setIsPaymentFiltersOpen] = React.useState(false) - - // hooks - const { - selectedAsset, - selectedCurrency, - selectedAccount, - amount, - isLoadingAssets, - isLoadingSpotPrices, - formattedCryptoEstimate, - spotPriceRegistry, - fiatCurrencies, - accounts, - cryptoCurrencies, - defaultFiatCurrency, - isFetchingQuotes, - quotes, - filteredQuotes, - onSelectToken, - onSelectAccount, - onSelectCurrency, - onSetAmount, - serviceProviders, - onFlipAmounts, - selectedCountryCode, - setSelectedCountryCode, - isLoadingPaymentMethods, - isLoadingCountries, - countries, - paymentMethods, - onChangePaymentMethods, - isCreatingWidget, - onBuy, - searchTerm, - onSearch - } = useBuy() - - // redux - const isPanel = useSafeUISelector(UISelectors.isPanel) - - // computed - const pageTitle = selectedAsset - ? `${getLocale('braveWalletBuy')} ${getAssetSymbol(selectedAsset)}` - : getLocale('braveWalletBuy') - - return ( - <> - } - hideDivider - noMinCardHeight - useDarkBackground={isPanel} - noPadding={isPanel} - noCardPadding={isPanel} - > - - - setIsAssetDialogOpen(true)} - /> - setIsAccountDialogOpen(true)} - /> - { - setIsCurrencyDialogOpen(true) - }} - onChange={onSetAmount} - estimatedCryptoAmount={formattedCryptoEstimate} - onFlipAmounts={onFlipAmounts} - /> - - - {isFetchingQuotes ? ( - - - Getting best prices... - - ) : ( - <> - {quotes?.length > 0 ? ( - <> - - - - onSearch(e.value)} - size='small' - > - - - - setIsPaymentFiltersOpen(true)} - > - - - - - ) : null} - {filteredQuotes?.length > 0 && serviceProviders?.length > 0 ? ( - - {quotes?.map((quote) => ( - - ))} - - ) : null} - - )} - - - - - { - onSelectToken(asset) - setIsAssetDialogOpen(false) - }} - onClose={() => setIsAssetDialogOpen(false)} - /> - - { - onSelectCurrency(currency) - setIsCurrencyDialogOpen(false) - }} - onClose={() => setIsCurrencyDialogOpen(false)} - /> - - { - onSelectAccount(account) - setIsAccountDialogOpen(false) - }} - onClose={() => setIsAccountDialogOpen(false)} - /> - - setIsPaymentFiltersOpen(false)} - /> - - ) -} diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.stories.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.stories.tsx similarity index 69% rename from components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.stories.tsx rename to components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.stories.tsx index a5c863dc8635..39f77433c32b 100644 --- a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v1.stories.tsx +++ b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.stories.tsx @@ -4,8 +4,12 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import * as React from 'react' -import WalletPageStory from '../../../stories/wrappers/wallet-page-story-wrapper' -import { FundWalletScreen } from './fund_wallet_v1' + +// Components +import { + WalletPageStory // +} from '../../../stories/wrappers/wallet-page-story-wrapper' +import { FundWalletScreen } from './fund_wallet_v2' export const _FundWalletScreen = () => { return ( @@ -16,10 +20,10 @@ export const _FundWalletScreen = () => { } _FundWalletScreen.story = { - name: 'Fund Wallet Screen v1' + name: 'Fund Wallet Screen v2' } export default { component: _FundWalletScreen, - title: 'Fund Wallet Screen v1' + title: 'Fund Wallet Screen v2' } diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.style.ts b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.style.ts new file mode 100644 index 000000000000..4e80f95671dc --- /dev/null +++ b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.style.ts @@ -0,0 +1,124 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +import styled from 'styled-components' +import { color, font } from '@brave/leo/tokens/css/variables' +import ProgressRing from '@brave/leo/react/progressRing' +import LeoDropdown from '@brave/leo/react/dropdown' +import Icon from '@brave/leo/react/icon' + +// Shared Styles +import { + layoutPanelWidth // +} from '../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style' +import { Column, Row } from '../../../components/shared/style' + +export const ContentWrapper = styled(Column)` + @media screen and (max-width: ${layoutPanelWidth}px) { + padding: 8px; + } +` + +export const ControlPanel = styled(Row)` + gap: 16px; + justify-content: space-between; + align-items: flex-start; + overflow: hidden; + flex-wrap: wrap; + padding: 0px 12px; + @media screen and (max-width: ${layoutPanelWidth}px) { + border-radius: 16px; + padding: 8px 20px 20px 20px; + background-color: ${color.container.background}; + justify-content: flex-start; + margin-bottom: 8px; + } +` + +export const ServiceProvidersWrapper = styled(Column)` + flex: 1; + gap: 16px; + @media screen and (max-width: ${layoutPanelWidth}px) { + gap: 8px; + border-radius: 16px; + background-color: ${color.container.background}; + padding: 8px; + } +` + +export const LoaderText = styled.p` + color: ${color.text.primary}; + font: ${font.default.regular}; + text-align: center; +` + +export const Loader = styled(ProgressRing).attrs({ + mode: 'indeterminate' +})` + --leo-progressring-size: 32px; +` + +export const Divider = styled.div` + width: 100%; + height: 1px; + background-color: ${color.divider.subtle}; + margin: 40px 0px 24px 0px; + @media screen and (max-width: ${layoutPanelWidth}px) { + display: none; + } +` + +export const PaymentMethodIcon = styled.img` + width: 20px; + height: 20px; + margin-right: 8px; +` + +export const SearchAndFilterRow = styled(Row)` + gap: 12px; + flex-wrap: wrap; + @media screen and (max-width: ${layoutPanelWidth}px) { + gap: 2px; + } +` + +export const SearchBarWrapper = styled(Row)` + max-width: 200px; + @media screen and (max-width: ${layoutPanelWidth}px) { + max-width: unset; + } +` + +export const DropdownRow = styled(Row)` + width: unset; + gap: 12px; + @media screen and (max-width: ${layoutPanelWidth}px) { + width: 100%; + gap: 4px; + } +` + +export const Dropdown = styled(LeoDropdown).attrs({ + size: window.innerWidth <= layoutPanelWidth ? 'small' : 'normal' +})` + width: unset; + @media screen and (max-width: ${layoutPanelWidth}px) { + width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + } +` + +export const InfoIconWrapper = styled(Column)` + width: 56px; + height: 56px; + border-radius: 100%; + background-color: ${color.page.background}; +` + +export const InfoIcon = styled(Icon).attrs({ name: 'info-outline' })` + --leo-icon-color: ${color.icon.default}; + --leo-icon-size: 24px; +` diff --git a/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.tsx b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.tsx new file mode 100644 index 000000000000..e03d90356a46 --- /dev/null +++ b/components/brave_wallet_ui/page/screens/fund-wallet/fund_wallet_v2.tsx @@ -0,0 +1,393 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import Icon from '@brave/leo/react/icon' + +// Types +import { WalletRoutes } from '../../../constants/types' + +// Hooks +import { useBuy } from './hooks/useBuy' + +// Utils +import { getLocale } from '../../../../common/locale' +import { getAssetSymbol } from '../../../utils/meld_utils' +import { isComponentInStorybook } from '../../../utils/string-utils' + +// Selectors +import { useSafeUISelector } from '../../../common/hooks/use-safe-selector' +import { UISelectors } from '../../../common/selectors' + +// Components +import { + WalletPageWrapper // +} from '../../../components/desktop/wallet-page-wrapper/wallet-page-wrapper' +import { + PageTitleHeader // +} from '../../../components/desktop/card-headers/page-title-header' +import { + PanelActionHeader // +} from '../../../components/desktop/card-headers/panel-action-header' +import { + SelectAssetButton // +} from './components/select_asset_button/select_asset_button' +import { + SelectAccountButton // +} from './components/select_account_button/select_account_button' +import { AmountButton } from './components/amount_button/amount_button' +import { SelectCurrency } from './components/select_currency/select_currency' +import { SelectAccount } from './components/select_account/select_account' +import { SelectAsset } from './components/select_asset/select_asset' +import { BuyQuote } from './components/buy_quote/buy_quote' + +// Styled Components +import { + ContentWrapper, + ControlPanel, + Divider, + Loader, + LoaderText, + ServiceProvidersWrapper, + PaymentMethodIcon, + SearchAndFilterRow, + SearchBarWrapper, + DropdownRow, + Dropdown, + InfoIconWrapper, + InfoIcon +} from './fund_wallet_v2.style' +import { Column, Row, Text } from '../../../components/shared/style' +import { SearchInput } from './components/shared/style' + +interface Props { + isAndroid?: boolean +} + +export const FundWalletScreen = ({ isAndroid }: Props) => { + // State + const [isCurrencyDialogOpen, setIsCurrencyDialogOpen] = React.useState(false) + const [isAssetDialogOpen, setIsAssetDialogOpen] = React.useState(false) + const [isAccountDialogOpen, setIsAccountDialogOpen] = React.useState(false) + + // Hooks + const { + selectedAsset, + selectedCurrency, + selectedAccount, + amount, + isLoadingAssets, + isLoadingSpotPrices, + formattedCryptoEstimate, + spotPriceRegistry, + fiatCurrencies, + accounts, + cryptoCurrencies, + defaultFiatCurrency, + isFetchingQuotes, + quotes, + filteredQuotes, + onSelectToken, + onSelectAccount, + onSelectCurrency, + onSetAmount, + serviceProviders, + selectedCountryCode, + isLoadingPaymentMethods, + isLoadingCountries, + countries, + paymentMethods, + selectedPaymentMethod, + onSelectPaymentMethod, + onSelectCountry, + isCreatingWidget, + onBuy, + searchTerm, + onSearch, + hasQuoteError + } = useBuy() + + // Redux + const isPanel = useSafeUISelector(UISelectors.isPanel) + + // Computed + const selectedCountry = countries?.find( + (country) => + country.countryCode.toLowerCase() === selectedCountryCode.toLowerCase() + ) + const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches + const isStorybook = isComponentInStorybook() + const pageTitle = getLocale('braveWalletBuyAsset').replace( + '$1', + getAssetSymbol(selectedAsset) + ) + const isFetchingFirstTimeQuotes = isFetchingQuotes && quotes?.length === 0 + + return ( + <> + + ) : ( + + ) + } + hideDivider + useDarkBackground={isPanel} + noPadding={isPanel} + noCardPadding={isPanel} + > + + + setIsAssetDialogOpen(true)} + /> + setIsAccountDialogOpen(true)} + /> + { + setIsCurrencyDialogOpen(true) + }} + onChange={onSetAmount} + estimatedCryptoAmount={formattedCryptoEstimate} + /> + + + + + + onSearch(e.value)} + size='small' + disabled={isFetchingFirstTimeQuotes} + > + + + + + onSelectCountry(detail.value as string)} + disabled={isFetchingFirstTimeQuotes || isLoadingCountries} + > +
{selectedCountry?.name}
+ {countries?.map((country) => { + return ( + + {country.name} + + ) + })} +
+ + onSelectPaymentMethod(detail.value as string) + } + disabled={ + isFetchingFirstTimeQuotes || isLoadingPaymentMethods + } + > +
{selectedPaymentMethod.name}
+ {paymentMethods?.map((paymentMethod) => { + const logoUrl = isDarkMode + ? paymentMethod.logoImages?.darkUrl + : paymentMethod.logoImages?.lightUrl + return ( + + + + {paymentMethod.name} + + + ) + })} +
+
+
+ {isFetchingFirstTimeQuotes ? ( + + + + {getLocale('braveWalletGettingBestPrices')} + + + ) : ( + <> + {hasQuoteError ? ( + + + + + + {getLocale('braveWalletNoProviderFound').replace( + '$1', + getAssetSymbol(selectedAsset) + )} + + + {getLocale('braveWalletTrySearchingForDifferentAsset')} + + + ) : ( + <> + {searchTerm !== '' && filteredQuotes.length === 0 ? ( + + + + + + {getLocale('braveWalletNoResultsFound').replace( + '$1', + searchTerm + )} + + + {getLocale('braveWalletTryDifferentKeywords')} + + + ) : ( + + {filteredQuotes?.map((quote, index) => ( + 1 && index === 0 + } + isOpenOverride={index === 0} + isCreatingWidget={isCreatingWidget} + onBuy={onBuy} + selectedAsset={selectedAsset} + /> + ))} + + )} + + )} + + )} +
+
+
+ + { + onSelectToken(asset) + setIsAssetDialogOpen(false) + }} + onClose={() => setIsAssetDialogOpen(false)} + /> + + { + onSelectCurrency(currency) + setIsCurrencyDialogOpen(false) + }} + onClose={() => setIsCurrencyDialogOpen(false)} + /> + + { + onSelectAccount(account) + setIsAccountDialogOpen(false) + }} + selectedAsset={selectedAsset} + onClose={() => setIsAccountDialogOpen(false)} + /> + + ) +} diff --git a/components/brave_wallet_ui/page/screens/swap/hooks/useBuy.ts b/components/brave_wallet_ui/page/screens/fund-wallet/hooks/useBuy.ts similarity index 61% rename from components/brave_wallet_ui/page/screens/swap/hooks/useBuy.ts rename to components/brave_wallet_ui/page/screens/fund-wallet/hooks/useBuy.ts index 4de1fc143b40..65261e66dd49 100644 --- a/components/brave_wallet_ui/page/screens/swap/hooks/useBuy.ts +++ b/components/brave_wallet_ui/page/screens/fund-wallet/hooks/useBuy.ts @@ -4,9 +4,10 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. import { useState, useEffect, useCallback, useMemo } from 'react' +import { useHistory } from 'react-router' import { skipToken } from '@reduxjs/toolkit/dist/query' -// types +// Types import { MeldCryptoCurrency, MeldFiatCurrency, @@ -17,7 +18,13 @@ import { CryptoWidgetCustomerData } from '../../../../constants/types' -// api +// Hooks +import { + useDebouncedCallback // +} from '../../swap/hooks/useDebouncedCallback' +import { useQuery } from '../../../../common/hooks/use-query' + +// Queries import { useGetDefaultFiatCurrencyQuery, useGetMeldFiatCurrenciesQuery, @@ -26,30 +33,38 @@ import { useGetDefaultCountryQuery, useGenerateMeldCryptoQuotesMutation, useGetTokenSpotPricesQuery, - walletApi, useGetMeldServiceProvidersQuery, useGetMeldPaymentMethodsQuery, useCreateMeldBuyWidgetMutation } from '../../../../common/slices/api.slice' -import { useAccountsQuery } from '../../../../common/slices/api.slice.extra' +import { + useAccountFromAddressQuery, + useAccountsQuery, + useReceiveAddressQuery +} from '../../../../common/slices/api.slice.extra' -// constants +// Constants import { querySubscriptionOptions60s } from '../../../../common/slices/constants' -// utils +// Utils import Amount from '../../../../utils/amount' -import { getAssetSymbol, getAssetPriceId } from '../../../../utils/meld_utils' -import { useDebouncedCallback } from './useDebouncedCallback' +import { + getAssetSymbol, + getAssetPriceId, + getMeldTokensCoinType +} from '../../../../utils/meld_utils' +import { makeFundWalletRoute } from '../../../../utils/routes-utils' export type BuyParamOverrides = { country?: string + paymentMethod?: MeldPaymentMethod sourceCurrencyCode?: string - destionationCurrencyCode?: string + destinationCurrencyCode?: string amount?: string account?: string } -const defaultAsset: MeldCryptoCurrency = { +const DEFAULT_ASSET: MeldCryptoCurrency = { 'currencyCode': 'ETH', 'name': 'Ethereum', 'chainCode': 'ETH', @@ -59,11 +74,36 @@ const defaultAsset: MeldCryptoCurrency = { 'symbolImageUrl': 'https://images-currency.meld.io/crypto/ETH/symbol.png' } +const DEFAULT_PAYMENT_METHOD: MeldPaymentMethod = { + logoImages: { + darkShortUrl: '', + darkUrl: + 'https://images-paymentMethod.meld.io/CREDIT_DEBIT_CARD/logo_dark.png', + lightShortUrl: '', + lightUrl: + 'https://images-paymentMethod.meld.io/CREDIT_DEBIT_CARD/logo_light.png' + }, + name: 'Credit & Debit Card', + paymentMethod: 'CREDIT_DEBIT_CARD', + paymentType: 'CARD' +} + +const getFirstAccountByCoinType = ( + coin: BraveWallet.CoinType, + accounts: BraveWallet.AccountInfo[] +) => { + return accounts.filter((account) => account.accountId.coin === coin)[0] +} + export const useBuy = () => { - // queries + // Routing + const history = useHistory() + const query = useQuery() + + // Queries const { data: defaultFiatCurrency = 'USD' } = useGetDefaultFiatCurrencyQuery() const { data: fiatCurrencies } = useGetMeldFiatCurrenciesQuery() - const { data: cryptoCurrencies, isLoading: isLoadingAssets } = + const { data: meldSupportedBuyAssets, isLoading: isLoadingAssets } = useGetMeldCryptoCurrenciesQuery() const { accounts } = useAccountsQuery() const { data: countries, isLoading: isLoadingCountries } = @@ -71,27 +111,22 @@ export const useBuy = () => { const { data: defaultCountryCode } = useGetDefaultCountryQuery() const { data: serviceProviders = [], isLoading: isLoadingServiceProvider } = useGetMeldServiceProvidersQuery() - const { data: paymentMethods, isLoading: isLoadingPaymentMethods } = - useGetMeldPaymentMethodsQuery() - - // mutations - const [generateQuotes] = useGenerateMeldCryptoQuotesMutation() - const [createMeldBuyWidget] = useCreateMeldBuyWidgetMutation() + const { account: accountFromParams } = useAccountFromAddressQuery( + query.get('accountId') ?? undefined + ) + const chainId = query.get('chainId') ?? undefined + const currencyCode = query.get('currencyCode') ?? undefined - // state - const [selectedAsset, setSelectedAsset] = - useState(defaultAsset) + // State const [selectedCurrency, setSelectedCurrency] = useState< MeldFiatCurrency | undefined >(undefined) - const [selectedAccount, setSelectedAccount] = - useState(accounts[0]) - const [amount, setAmount] = useState('') + const [amount, setAmount] = useState('100') const [abortController, setAbortController] = useState< AbortController | undefined >(undefined) const [isFetchingQuotes, setIsFetchingQuotes] = useState(false) - const [buyErrors, setBuyErrors] = useState([]) + const [hasQuoteError, setHasQuoteError] = useState(false) const [quotes, setQuotes] = useState([]) const [timeUntilNextQuote, setTimeUntilNextQuote] = useState< number | undefined @@ -99,16 +134,28 @@ export const useBuy = () => { const [selectedCountryCode, setSelectedCountryCode] = useState( defaultCountryCode || 'US' ) - const [selectedPaymentMethods, setSelectedPaymentMethods] = useState< - MeldPaymentMethod[] - >(paymentMethods || []) + const [selectedPaymentMethod, setSelectedPaymentMethod] = + useState(DEFAULT_PAYMENT_METHOD) const [isCreatingWidget, setIsCreatingWidget] = useState(false) const [searchTerm, setSearchTerm] = useState('') - // computed + // Mutations + const [generateQuotes] = useGenerateMeldCryptoQuotesMutation() + const [createMeldBuyWidget] = useCreateMeldBuyWidgetMutation() + const { data: paymentMethods, isLoading: isLoadingPaymentMethods } = + useGetMeldPaymentMethodsQuery( + selectedCountryCode && defaultFiatCurrency + ? { + country: selectedCountryCode, + sourceCurrencyCode: defaultFiatCurrency + } + : skipToken + ) + + // Memos and Queries const tokenPriceIds: string[] = useMemo(() => { - return cryptoCurrencies?.map((asset) => getAssetPriceId(asset)) ?? [] - }, [cryptoCurrencies]) + return meldSupportedBuyAssets?.map((asset) => getAssetPriceId(asset)) ?? [] + }, [meldSupportedBuyAssets]) const { data: spotPriceRegistry, isLoading: isLoadingSpotPrices } = useGetTokenSpotPricesQuery( @@ -121,7 +168,29 @@ export const useBuy = () => { querySubscriptionOptions60s ) - // memos + const selectedAsset = useMemo(() => { + if (!currencyCode || !meldSupportedBuyAssets || !chainId) { + return DEFAULT_ASSET + } + + return ( + meldSupportedBuyAssets.find( + (asset) => + asset.currencyCode === currencyCode && asset.chainId === chainId + ) ?? DEFAULT_ASSET + ) + }, [meldSupportedBuyAssets, currencyCode, chainId]) + + const selectedAccount = useMemo(() => { + if (!accountFromParams) { + return getFirstAccountByCoinType( + getMeldTokensCoinType(selectedAsset), + accounts + ) + } + return accountFromParams + }, [accountFromParams, accounts, selectedAsset]) + const selectedAssetSpotPrice = useMemo(() => { if (selectedAsset && spotPriceRegistry) { return spotPriceRegistry[getAssetPriceId(selectedAsset)] @@ -142,23 +211,38 @@ export const useBuy = () => { return ['', ''] }, [selectedAssetSpotPrice, selectedAsset, amount]) + const quotesSortedByBestReturn = useMemo(() => { + if (quotes.length === 0) { + return [] + } + return Array.from(quotes).sort(function (a, b) { + return new Amount(b.destinationAmount ?? '0') + .minus(a.destinationAmount ?? '0') + .toNumber() + }) + }, [quotes]) + const filteredQuotes = useMemo(() => { if (searchTerm === '') { - return quotes + return quotesSortedByBestReturn } - return quotes.filter((quote) => - quote.serviceProvider?.toLowerCase().includes(searchTerm.toLowerCase()) + return quotesSortedByBestReturn.filter( + (quote) => + quote.serviceProvider + ?.toLowerCase() + .startsWith(searchTerm.toLowerCase()) || + quote.serviceProvider?.toLowerCase().includes(searchTerm.toLowerCase()) ) - }, [quotes, searchTerm]) + }, [quotesSortedByBestReturn, searchTerm]) - // methods + const { receiveAddress } = useReceiveAddressQuery(selectedAccount?.accountId) + + // Methods const reset = useCallback(() => { - setSelectedAsset(defaultAsset) setSelectedCurrency(undefined) - setSelectedAccount(accounts[0]) setAmount('') - setBuyErrors([]) + setHasQuoteError(false) setQuotes([]) setTimeUntilNextQuote(undefined) @@ -166,33 +250,24 @@ export const useBuy = () => { abortController.abort() setAbortController(undefined) } - }, [abortController, accounts]) + }, [abortController]) const handleQuoteRefreshInternal = useCallback( async (overrides: BuyParamOverrides) => { const params = { - country: - overrides.country === undefined - ? defaultCountryCode - : overrides.country, + country: overrides.country ?? defaultCountryCode, sourceCurrencyCode: - overrides.sourceCurrencyCode === undefined - ? selectedCurrency?.currencyCode - : overrides.sourceCurrencyCode, - destionationCurrencyCode: - overrides.destionationCurrencyCode === undefined - ? selectedAsset?.currencyCode - : overrides.destionationCurrencyCode, - amount: overrides.amount === undefined ? amount : overrides.amount, - account: - overrides.account === undefined - ? selectedAccount.address - : overrides.account + overrides.sourceCurrencyCode ?? selectedCurrency?.currencyCode, + destinationCurrencyCode: + overrides.destinationCurrencyCode ?? selectedAsset?.currencyCode, + amount: overrides.amount ?? amount, + account: overrides.account ?? selectedAccount.address, + paymentMethod: overrides.paymentMethod ?? selectedPaymentMethod } if ( !params.sourceCurrencyCode || - !params.destionationCurrencyCode || + !params.destinationCurrencyCode || !params.account ) { return @@ -210,17 +285,18 @@ export const useBuy = () => { const controller = new AbortController() setAbortController(controller) + setIsFetchingQuotes(true) + setHasQuoteError(false) let quoteResponse try { - setIsFetchingQuotes(true) quoteResponse = await generateQuotes({ account: selectedAccount.address, amount: amountWrapped.toNumber(), country: defaultCountryCode || 'US', sourceCurrencyCode: params.sourceCurrencyCode, - destionationCurrencyCode: params.destionationCurrencyCode, - paymentMethods: selectedPaymentMethods + destinationCurrencyCode: params.destinationCurrencyCode, + paymentMethod: params.paymentMethod }).unwrap() } catch (error) { console.error('generateQuotes failed', error) @@ -235,7 +311,7 @@ export const useBuy = () => { if (quoteResponse?.error) { console.error('quoteResponse.error', quoteResponse.error) - setBuyErrors(quoteResponse.error) + setHasQuoteError(true) } if (quoteResponse?.cryptoQuotes) { @@ -244,7 +320,7 @@ export const useBuy = () => { setIsFetchingQuotes(false) setAbortController(undefined) - setTimeUntilNextQuote(10000) + setTimeUntilNextQuote(30000) }, [ amount, @@ -253,7 +329,7 @@ export const useBuy = () => { selectedAccount?.address, selectedAsset?.currencyCode, selectedCurrency?.currencyCode, - selectedPaymentMethods + selectedPaymentMethod ] ) @@ -280,47 +356,68 @@ export const useBuy = () => { [handleQuoteRefresh] ) + const onSelectCountry = useCallback( + async (countryCode: string) => { + setSelectedCountryCode(countryCode) + setQuotes([]) + + await handleQuoteRefresh({ + country: countryCode + }) + }, + [handleQuoteRefresh] + ) + + const onSelectPaymentMethod = useCallback( + async (paymentMethod: string) => { + const foundMethod = + paymentMethods?.find( + (method) => method.paymentMethod === paymentMethod + ) ?? DEFAULT_PAYMENT_METHOD + setSelectedPaymentMethod(foundMethod) + setQuotes([]) + + await handleQuoteRefresh({ + paymentMethod: foundMethod + }) + }, + [handleQuoteRefresh, paymentMethods] + ) + const onSelectCurrency = useCallback( async (currency: MeldFiatCurrency) => { setSelectedCurrency(currency) - if (selectedAsset) { - // TODO(william): Fetch only the spot price for the selected asset - walletApi.util.invalidateTags([ - { - type: 'TokenSpotPrices', - id: getAssetPriceId(selectedAsset) - } - ]) - } - await handleQuoteRefresh({ sourceCurrencyCode: currency.currencyCode }) }, - [handleQuoteRefresh, selectedAsset] + [handleQuoteRefresh] ) const onSelectToken = useCallback( async (asset: MeldCryptoCurrency) => { - console.log('setting asset to: ', asset) - setSelectedAsset(asset) + const incomingAssetsCoinType = getMeldTokensCoinType(asset) + const accountToUse = + selectedAccount.accountId.coin !== incomingAssetsCoinType + ? getFirstAccountByCoinType(incomingAssetsCoinType, accounts) + : selectedAccount + history.replace(makeFundWalletRoute(asset, accountToUse)) + setQuotes([]) await handleQuoteRefresh({ - destionationCurrencyCode: asset?.currencyCode + destinationCurrencyCode: asset?.currencyCode }) }, - [handleQuoteRefresh] + [handleQuoteRefresh, history, selectedAccount, accounts] ) - const onFlipAmounts = useCallback(async () => { - if (!cryptoEstimate) return - - setAmount(cryptoEstimate) - await handleQuoteRefresh({ - amount: cryptoEstimate - }) - }, [cryptoEstimate, handleQuoteRefresh]) + const onSelectAccount = useCallback( + (account: BraveWallet.AccountInfo) => { + history.replace(makeFundWalletRoute(selectedAsset, account)) + }, + [selectedAsset, history] + ) const onBuy = useCallback( async (quote: MeldCryptoQuote) => { @@ -334,7 +431,7 @@ export const useBuy = () => { serviceProvider: quote.serviceProvider, sourceAmount: amount, sourceCurrencyCode: selectedCurrency.currencyCode, - walletAddress: selectedAccount.address, + walletAddress: receiveAddress, walletTag: undefined, lockFields: ['walletAddress'] } @@ -366,20 +463,14 @@ export const useBuy = () => { [ amount, createMeldBuyWidget, - selectedAccount?.address, selectedAsset?.currencyCode, selectedCountryCode, - selectedCurrency + selectedCurrency, + receiveAddress ] ) - // effects - useEffect(() => { - if (accounts.length > 0 && !selectedAccount) { - setSelectedAccount(accounts[0]) - } - }, [accounts, selectedAccount]) - + // Effects useEffect(() => { if (fiatCurrencies && fiatCurrencies.length > 0 && !selectedCurrency) { const defaultCurrency = fiatCurrencies.find( @@ -391,7 +482,7 @@ export const useBuy = () => { } }, [defaultFiatCurrency, fiatCurrencies, selectedCurrency]) - // fetch quotes in intervals + // Fetch quotes in intervals useEffect(() => { const interval = setInterval(async () => { if (timeUntilNextQuote && timeUntilNextQuote !== 0) { @@ -414,39 +505,35 @@ export const useBuy = () => { amount, isLoadingAssets, isLoadingSpotPrices, - cryptoEstimate, formattedCryptoEstimate, - selectedAssetSpotPrice, spotPriceRegistry, fiatCurrencies, - cryptoCurrencies, + cryptoCurrencies: meldSupportedBuyAssets, countries, accounts, - defaultCountryCode, defaultFiatCurrency, - handleQuoteRefreshInternal, isFetchingQuotes, quotes, filteredQuotes, - buyErrors, - timeUntilNextQuote, onSelectToken, - onSelectAccount: setSelectedAccount, + onSelectAccount, onSelectCurrency, onSetAmount, serviceProviders, - isLoadingServiceProvider, - onFlipAmounts, selectedCountryCode, - setSelectedCountryCode, - reset, isLoadingPaymentMethods, isLoadingCountries, paymentMethods, - onChangePaymentMethods: setSelectedPaymentMethods, + selectedPaymentMethod, + onSelectCountry, + onSelectPaymentMethod, onBuy, isCreatingWidget, searchTerm, - onSearch: setSearchTerm + onSearch: setSearchTerm, + cryptoEstimate, + hasQuoteError, + isLoadingServiceProvider, + reset } } diff --git a/components/brave_wallet_ui/stories/locale.ts b/components/brave_wallet_ui/stories/locale.ts index 1418d9bc3e81..e4e3c4e468ad 100644 --- a/components/brave_wallet_ui/stories/locale.ts +++ b/components/brave_wallet_ui/stories/locale.ts @@ -579,7 +579,7 @@ provideStrings({ braveWalletSelectAccount: 'Select account', braveWalletSearchAccount: 'Search accounts', braveWalletSelectNetwork: 'Select network', - braveWalletSelectAsset: 'Select from', + braveWalletSelectAsset: 'Select asset', braveWalletSearchAsset: 'Search coins', braveWalletSelectCurrency: 'Select currency', braveWalletSearchCurrency: 'Search currencies', @@ -668,6 +668,30 @@ provideStrings({ braveWalletBuyDisclaimer: 'Financial and transaction data is processed by our onramp partners. ' + 'Brave does not collect or have access to such data.', + braveWalletTransactionsPartner: 'Transactions partner', + braveWalletTransactionPartnerConsent: + 'Brave Wallet uses Meld.io to help aggregate and surface various ' + + 'crypto providers for your region. We will share information with ' + + 'Meld.io to complete the transaction, including your wallet address ' + + 'and country code. For more information please read Meld’s terms of use.', + braveWalletMeldTermsOfUse: + 'I have read and agree to the $1Meld’s Terms of use$2', + braveWalletBestOption: 'Best Option', + braveWalletExchangeRateWithFees: 'Exchange rate with fees', + braveWalletFees: 'Fees', + braveWalletPriceCurrency: 'Price $1', + braveWalletBuyWithProvider: 'Buy with $1', + braveWalletAsset: 'Asset', + braveWalletSelected: 'Selected', + braveWalletNoAvailableCurrencies: 'No available currencies', + braveWalletGettingBestPrices: 'Getting best prices...', + braveWalletBuyAsset: 'Buy $1', + braveWalletNoProviderFound: 'No providers found for $1', + braveWalletTrySearchingForDifferentAsset: + 'Try searching for a different asset.', + braveWalletNoResultsFound: 'No results found for $1', + braveWalletTryDifferentKeywords: + 'Try using a different keyword or check your spelling.', // Fund Wallet Screen braveWalletFundWalletTitle: diff --git a/components/brave_wallet_ui/utils/coin-market-utils.ts b/components/brave_wallet_ui/utils/coin-market-utils.ts index 756b608d952e..276cdb2682bc 100644 --- a/components/brave_wallet_ui/utils/coin-market-utils.ts +++ b/components/brave_wallet_ui/utils/coin-market-utils.ts @@ -7,8 +7,10 @@ import { BraveWallet, MarketAssetFilterOption, MarketGridColumnTypes, + MeldCryptoCurrency, SortOrder } from '../constants/types' +import { getAssetSymbol } from './meld_utils' export const sortCoinMarkets = ( marketData: BraveWallet.CoinMarket[], @@ -40,18 +42,18 @@ export const searchCoinMarkets = ( export const filterCoinMarkets = ( coins: BraveWallet.CoinMarket[], - tradableAssets: BraveWallet.BlockchainToken[], + tradableAssets: MeldCryptoCurrency[] | undefined, filter: MarketAssetFilterOption ) => { - const tradableAssetsSymbols = tradableAssets.map((asset) => - asset.symbol.toLowerCase() + const tradableAssetsSymbols = tradableAssets?.map((asset) => + getAssetSymbol(asset).toLowerCase() ) if (filter === 'all') { return coins } else if (filter === 'tradable') { return coins.filter((asset) => - tradableAssetsSymbols.includes(asset.symbol.toLowerCase()) + tradableAssetsSymbols?.includes(asset.symbol.toLowerCase()) ) } diff --git a/components/brave_wallet_ui/utils/meld_utils.ts b/components/brave_wallet_ui/utils/meld_utils.ts index dab19a28acc7..0e91ea79ad60 100644 --- a/components/brave_wallet_ui/utils/meld_utils.ts +++ b/components/brave_wallet_ui/utils/meld_utils.ts @@ -31,7 +31,39 @@ export const getTokenPriceFromRegistry = ( } export const getAssetIdKey = (asset: MeldCryptoCurrency) => { - return `0x${parseInt(asset.chainId ?? '').toString(16)}-${asset.currencyCode}-${ - asset.contractAddress - }` + return `0x${parseInt(asset.chainId ?? '').toString(16)}-${ + asset.currencyCode + }-${asset.contractAddress}` +} + +export const getMeldTokensCoinType = ( + asset: Pick +) => { + switch (asset.chainCode) { + case 'BTC': + return BraveWallet.CoinType.BTC + case 'FIL': + return BraveWallet.CoinType.FIL + case 'ZEC': + return BraveWallet.CoinType.ZEC + case 'SOLANA': + return BraveWallet.CoinType.SOL + default: + return BraveWallet.CoinType.ETH + } +} + +export const getMeldTokensChainId = ( + asset: Pick +) => { + switch (asset.chainCode) { + case 'BTC': + return BraveWallet.BITCOIN_MAINNET + case 'FIL': + return BraveWallet.FILECOIN_MAINNET + case 'ZEC': + return BraveWallet.Z_CASH_MAINNET + default: + return asset.chainId + } } diff --git a/components/brave_wallet_ui/utils/routes-utils.ts b/components/brave_wallet_ui/utils/routes-utils.ts index c1f13aec8692..dca412d3feac 100644 --- a/components/brave_wallet_ui/utils/routes-utils.ts +++ b/components/brave_wallet_ui/utils/routes-utils.ts @@ -11,7 +11,8 @@ import { WalletOrigin, WalletCreationMode, WalletImportMode, - NftDropdownOptionId + NftDropdownOptionId, + MeldCryptoCurrency } from '../constants/types' import { LOCAL_STORAGE_KEYS } from '../common/constants/local-storage-keys' import { SUPPORT_LINKS } from '../common/constants/support_links' @@ -108,40 +109,21 @@ export const makeAccountTransactionRoute = ( } export const makeFundWalletRoute = ( - assetId: string, - options?: { - currencyCode?: string - buyAmount?: string - searchText?: string - chainId?: string - coinType?: string - } + asset: Pick, + account?: BraveWallet.AccountInfo ) => { - if (options) { - const params = new URLSearchParams() + const baseQueryParams = { + currencyCode: asset.currencyCode ?? '', + chainId: asset.chainId ?? '' + } - if (options.currencyCode) { - params.append('currencyCode', options.currencyCode) - } - if (options.buyAmount) { - params.append('buyAmount', options.buyAmount) - } - if (options.searchText) { - params.append('search', options.searchText) - } - if (options.chainId) { - params.append('chainId', options.chainId) - } - if (options.coinType) { - params.append('coinType', options.coinType) - } + const params = new URLSearchParams( + account + ? { ...baseQueryParams, accountId: account.accountId.uniqueKey } + : baseQueryParams + ) - return `${WalletRoutes.FundWalletPage.replace( - ':assetId?', - assetId - )}?${params.toString()}` - } - return WalletRoutes.FundWalletPage.replace(':assetId?', assetId) + return `${WalletRoutes.FundWalletPageStart}?${params.toString()}` } export const makeFundWalletPurchaseOptionsRoute = ( diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index 3e4f2dfd99c0..c20f3eadf881 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -190,12 +190,12 @@ export const capitalizeFirstLetter = (input: string) => { } export const reduceInt = (integerString: string) => { - if (integerString.length < 7) { - return integerString - } + if (integerString.length < 7) { + return integerString + } - const firstHalf = integerString.slice(0, 3) - const secondHalf = integerString.slice(-3) - const reduced = firstHalf.concat('...', secondHalf) - return reduced + const firstHalf = integerString.slice(0, 3) + const secondHalf = integerString.slice(-3) + const reduced = firstHalf.concat('...', secondHalf) + return reduced } diff --git a/components/resources/wallet_strings.grdp b/components/resources/wallet_strings.grdp index 9ddcda5fbf95..6c82f90e2253 100644 --- a/components/resources/wallet_strings.grdp +++ b/components/resources/wallet_strings.grdp @@ -344,7 +344,7 @@ Select account Search accounts Select network - Select from + Select asset Search coins Amount To @@ -618,6 +618,23 @@ Sell with $1Ramp Buy with Sardine Financial and transaction data is processed by our onramp partners. Brave does not collect or have access to such data. + Transactions partner + Brave Wallet uses Meld.io to help aggregate and surface various crypto providers for your region. We will share information with Meld.io to complete the transaction, including your wallet address and country code. For more information please read Meld’s terms of use. + I have read and agree to the $1Meld's Terms of use$2 + Best Option + Exchange rate with fees + Fees + Price $1USD + Buy with $1Ramp + Asset + Selected + No available currencies + Getting best prices... + Buy $1BAT + No providers found for $1BAT + Try searching for a different asset. + No results found for $1Ramp + Try using a different keyword or check your spelling. Buy with Transak Buy with Link Buy with Coinbase Pay