From c856e3e9581a75a9e93ad155e768dcbddbef4520 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Mon, 20 Oct 2025 16:31:39 -0700 Subject: [PATCH 1/8] Re-enable max button on RampCreateScene --- src/components/scenes/RampCreateScene.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index 9e509523ca5..38f3f030084 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -998,8 +998,6 @@ const getStyles = cacheStyles((theme: ReturnType) => ({ marginRight: theme.rem(0.5) }, maxButton: { - // TODO: Uncomment this when we have a max feature's bugs fixed and ready to ship - display: 'none', padding: theme.rem(0.25), borderRadius: theme.rem(0.5), borderColor: theme.escapeButtonText From 46fa26ea8b975f3e5d99dbc5eb69d3ca2d5a9d59 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 16:30:45 -0700 Subject: [PATCH 2/8] Factor out region selection elements into RampRegionSelect component --- src/components/scenes/RampCreateScene.tsx | 147 ++--------------- .../RampCreateScene/RampRegionSelect.tsx | 150 ++++++++++++++++++ 2 files changed, 162 insertions(+), 135 deletions(-) create mode 100644 src/components/scenes/RampCreateScene/RampRegionSelect.tsx diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index 38f3f030084..ca62de8e778 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -6,7 +6,6 @@ import { useState } from 'react' import { ActivityIndicator, Text, View } from 'react-native' import FastImage from 'react-native-fast-image' import { ShadowedView } from 'react-native-fast-shadow' -import Feather from 'react-native-vector-icons/Feather' import { sprintf } from 'sprintf-js' import { showCountrySelectionModal } from '../../actions/CountryListActions' @@ -65,6 +64,7 @@ import { Airship, showToast } from '../services/AirshipInstance' import { cacheStyles, useTheme } from '../services/ThemeContext' import { EdgeText } from '../themed/EdgeText' import { FilledTextInput } from '../themed/FilledTextInput' +import { RampRegionSelect } from './RampCreateScene/RampRegionSelect' export interface RampCreateParams { forcedWalletResult?: WalletListWalletResult @@ -479,16 +479,13 @@ export const RampCreateScene: React.FC = (props: Props) => { // const handleRegionSelect = useHandler(async () => { - if (account != null) { - await dispatch( - showCountrySelectionModal({ - account, - countryCode: countryCode !== '' ? countryCode : '', - stateProvinceCode - }) - ) - // After selection, the settings will update and shouldShowRegionSelect will recompute to false - } + await dispatch( + showCountrySelectionModal({ + account, + countryCode: countryCode !== '' ? countryCode : '', + stateProvinceCode + }) + ) }) const handleCryptDropdown = useHandler(async () => { @@ -652,80 +649,10 @@ export const RampCreateScene: React.FC = (props: Props) => { // Render region selection view if (shouldShowRegionSelect) { return ( - - - - {lstrings.trade_region_select_start_steps} - - - - - - {sprintf(lstrings.step_prefix_s, '1')} - - - {lstrings.trade_region_select_step_1} - - - - - {sprintf(lstrings.step_prefix_s, '2')} - - - {lstrings.trade_region_select_step_2} - - - - - {sprintf(lstrings.step_prefix_s, '3')} - - - {lstrings.trade_region_select_step_3} - - - - - {sprintf(lstrings.step_prefix_s, '4')} - - - {lstrings.trade_region_select_step_4} - - - - - - {flagUri != null ? ( - - ) : ( - - )} - - {getRegionText()} - - - - - + ) } @@ -1022,56 +949,6 @@ const getStyles = cacheStyles((theme: ReturnType) => ({ textAlign: 'center' as const, marginBottom: theme.rem(1) }, - stepsCard: { - marginHorizontal: theme.rem(0.5), - marginVertical: theme.rem(0.5), - padding: theme.rem(1), - backgroundColor: theme.cardBaseColor, - borderRadius: theme.rem(0.5), - borderWidth: theme.thinLineWidth, - borderColor: theme.cardBorderColor - }, - stepRow: { - flexDirection: 'row' as const, - alignItems: 'flex-start' as const, - marginVertical: theme.rem(0.25), - gap: theme.rem(0.5) - }, - stepNumberText: { - fontWeight: '600' as const, - minWidth: theme.rem(1.25) - }, - stepText: { - flex: 1 - }, - regionButton: { - flexDirection: 'row' as const, - alignItems: 'center' as const, - backgroundColor: theme.cardBaseColor, - borderRadius: theme.rem(0.5), - margin: theme.rem(0.5), - padding: theme.rem(1), - borderWidth: theme.thinLineWidth, - borderColor: theme.cardBorderColor, - gap: theme.rem(0.5) - }, - regionButtonText: { - flexShrink: 1, - color: theme.primaryText, - fontSize: theme.rem(1.1), - fontFamily: theme.fontFaceDefault - }, - globeIcon: { - marginRight: theme.rem(0.75) - }, - subtitleText: { - color: theme.primaryText, - fontSize: theme.rem(1.25), - fontFamily: theme.fontFaceDefault, - marginTop: theme.rem(1), - marginBottom: theme.rem(0.5), - marginHorizontal: theme.rem(0.5) - }, shadowedIcon: { width: theme.rem(1.5), height: theme.rem(1.5), diff --git a/src/components/scenes/RampCreateScene/RampRegionSelect.tsx b/src/components/scenes/RampCreateScene/RampRegionSelect.tsx new file mode 100644 index 00000000000..d46b6986781 --- /dev/null +++ b/src/components/scenes/RampCreateScene/RampRegionSelect.tsx @@ -0,0 +1,150 @@ +import * as React from 'react' +import { View } from 'react-native' +import Feather from 'react-native-vector-icons/Feather' +import { sprintf } from 'sprintf-js' + +import { useHandler } from '../../../hooks/useHandler' +import { lstrings } from '../../../locales/strings' +import { EdgeTouchableOpacity } from '../../common/EdgeTouchableOpacity' +import { SceneWrapper } from '../../common/SceneWrapper' +import { SceneContainer } from '../../layout/SceneContainer' +import { cacheStyles, useTheme } from '../../services/ThemeContext' +import { EdgeText } from '../../themed/EdgeText' + +interface Props { + headerTitle: string + onRegionSelect: () => Promise +} + +export const RampRegionSelect: React.FC = props => { + const { headerTitle, onRegionSelect } = props + const theme = useTheme() + const styles = getStyles(theme) + + const handleRegionSelect = useHandler(async () => { + await onRegionSelect() + }) + + return ( + + + + {lstrings.trade_region_select_start_steps} + + + + + + {sprintf(lstrings.step_prefix_s, '1')} + + + {lstrings.trade_region_select_step_1} + + + + + {sprintf(lstrings.step_prefix_s, '2')} + + + {lstrings.trade_region_select_step_2} + + + + + {sprintf(lstrings.step_prefix_s, '3')} + + + {lstrings.trade_region_select_step_3} + + + + + {sprintf(lstrings.step_prefix_s, '4')} + + + {lstrings.trade_region_select_step_4} + + + + + + + + {lstrings.buy_sell_crypto_select_country_button} + + + + + + ) +} + +const getStyles = cacheStyles((theme: ReturnType) => ({ + stepsCard: { + marginHorizontal: theme.rem(0.5), + marginVertical: theme.rem(0.5), + padding: theme.rem(1), + backgroundColor: theme.cardBaseColor, + borderRadius: theme.rem(0.5), + borderWidth: theme.thinLineWidth, + borderColor: theme.cardBorderColor + }, + stepRow: { + flexDirection: 'row' as const, + alignItems: 'flex-start' as const, + marginVertical: theme.rem(0.25), + gap: theme.rem(0.5) + }, + stepNumberText: { + fontWeight: '600' as const, + minWidth: theme.rem(1.25) + }, + stepText: { + flex: 1 + }, + regionButton: { + flexDirection: 'row' as const, + alignItems: 'center' as const, + backgroundColor: theme.cardBaseColor, + borderRadius: theme.rem(0.5), + margin: theme.rem(0.5), + padding: theme.rem(1), + borderWidth: theme.thinLineWidth, + borderColor: theme.cardBorderColor, + gap: theme.rem(0.5) + }, + regionButtonText: { + flexShrink: 1, + color: theme.primaryText, + fontSize: theme.rem(1.1), + fontFamily: theme.fontFaceDefault + }, + globeIcon: { + marginRight: theme.rem(0.75) + }, + subtitleText: { + color: theme.primaryText, + fontSize: theme.rem(1.25), + fontFamily: theme.fontFaceDefault, + marginTop: theme.rem(1), + marginBottom: theme.rem(0.5), + marginHorizontal: theme.rem(0.5) + } +})) From 0d9c822ae9115b2fdd50446dbbfefeda4449e68a Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 16:34:07 -0700 Subject: [PATCH 3/8] Inline helper functions --- src/components/scenes/RampCreateScene.tsx | 61 +++++++---------------- 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index ca62de8e778..46d19e704fb 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -386,38 +386,6 @@ export const RampCreateScene: React.FC = (props: Props) => { } }, [bestQuote]) - // Helper function to convert crypto amount to fiat using quote rate - const convertCryptoToFiat = React.useCallback( - (cryptoAmt: string): string => { - if (cryptoAmt === '' || quoteExchangeRate === 0) return '' - - try { - return div(mul(cryptoAmt, quoteExchangeRate.toString()), '1', 2) - } catch { - return '' - } - }, - [quoteExchangeRate] - ) - - // Helper function to convert fiat amount to crypto using quote rate - const convertFiatToCrypto = React.useCallback( - (fiatAmt: string): string => { - if (fiatAmt === '' || quoteExchangeRate === 0) return '' - - const decimals = - denomination != null - ? mulToPrecision(denomination.multiplier) - : DECIMAL_PRECISION - try { - return div(fiatAmt, quoteExchangeRate.toString(), decimals) - } catch { - return '' - } - }, - [denomination, quoteExchangeRate] - ) - // Derived state for display values const displayFiatAmount = React.useMemo(() => { // Don't show any value if fiat input is disabled @@ -432,16 +400,18 @@ export const RampCreateScene: React.FC = (props: Props) => { // User entered fiat, show raw value (FilledTextInput will format it) return userInput } else { + // Avoid division by zero + if (quoteExchangeRate === 0) return '' // User entered crypto, convert to fiat only if we have a quote - return convertCryptoToFiat(userInput) + return div(mul(userInput, quoteExchangeRate.toString()), '1', 2) } }, [ - userInput, - lastUsedInput, - convertCryptoToFiat, + fiatInputDisabled, isMaxAmount, maxQuoteForMaxFlow, - fiatInputDisabled + userInput, + lastUsedInput, + quoteExchangeRate ]) const displayCryptoAmount = React.useMemo(() => { @@ -457,16 +427,23 @@ export const RampCreateScene: React.FC = (props: Props) => { // User entered crypto, show raw value (FilledTextInput will format it) return userInput } else { + // Avoid division by zero + if (quoteExchangeRate === 0) return '' + const decimals = + denomination != null + ? mulToPrecision(denomination.multiplier) + : DECIMAL_PRECISION // User entered fiat, convert to crypto only if we have a quote - return convertFiatToCrypto(userInput) + return div(userInput, quoteExchangeRate.toString(), decimals) } }, [ - userInput, - lastUsedInput, - convertFiatToCrypto, + cryptoInputDisabled, isMaxAmount, maxQuoteForMaxFlow, - cryptoInputDisabled + userInput, + lastUsedInput, + quoteExchangeRate, + denomination ]) // Log the quote event only when the scene is focused From 072964c261b10cfdd6bd34b1f9b83da1d0741e37 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 16:40:29 -0700 Subject: [PATCH 4/8] Rename userInput to exchangeAmount --- src/components/scenes/RampCreateScene.tsx | 52 +++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index 46d19e704fb..8c78686e25d 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -102,7 +102,7 @@ export const RampCreateScene: React.FC = (props: Props) => { ) // State for trade form - const [userInput, setUserInput] = useState('') + const [exchangeAmount, setExchangeAmount] = useState('') const [lastUsedInput, setLastUsedInput] = useState<'fiat' | 'crypto' | null>( null ) @@ -246,7 +246,7 @@ export const RampCreateScene: React.FC = (props: Props) => { if ( hasAppliedInitialAmount.current || fiatInputDisabled || - userInput !== '' || + exchangeAmount !== '' || lastUsedInput != null || shouldShowRegionSelect ) { @@ -265,7 +265,7 @@ export const RampCreateScene: React.FC = (props: Props) => { ) hasAppliedInitialAmount.current = true - setUserInput(initialFiat) + setExchangeAmount(initialFiat) setLastUsedInput('fiat') } @@ -281,8 +281,8 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedCryptoCurrencyCode, selectedFiatCurrencyCode, shouldShowRegionSelect, - userInput, - fiatUsdRate + fiatUsdRate, + exchangeAmount ]) // Create rampQuoteRequest based on current form state @@ -291,7 +291,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet == null || selectedCryptoCurrencyCode == null || lastUsedInput == null || - (userInput === '' && !isMaxAmount) || + (exchangeAmount === '' && !isMaxAmount) || countryCode === '' ) { return null @@ -310,7 +310,7 @@ export const RampCreateScene: React.FC = (props: Props) => { pluginId: selectedWallet.currencyInfo.pluginId, tokenId: selectedCrypto?.tokenId ?? null, displayCurrencyCode: selectedCryptoCurrencyCode, - exchangeAmount: isMaxAmount ? { max: true } : userInput, + exchangeAmount: isMaxAmount ? { max: true } : exchangeAmount, fiatCurrencyCode: selectedFiatCurrencyCode, amountType: lastUsedInput, direction, @@ -323,7 +323,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet, selectedCryptoCurrencyCode, selectedCrypto, - userInput, + exchangeAmount, isMaxAmount, selectedFiatCurrencyCode, lastUsedInput, @@ -394,22 +394,22 @@ export const RampCreateScene: React.FC = (props: Props) => { if (isMaxAmount && maxQuoteForMaxFlow != null) { return maxQuoteForMaxFlow.fiatAmount ?? '' } - if (userInput === '' || lastUsedInput === null) return '' + if (exchangeAmount === '' || lastUsedInput === null) return '' if (lastUsedInput === 'fiat') { // User entered fiat, show raw value (FilledTextInput will format it) - return userInput + return exchangeAmount } else { // Avoid division by zero if (quoteExchangeRate === 0) return '' // User entered crypto, convert to fiat only if we have a quote - return div(mul(userInput, quoteExchangeRate.toString()), '1', 2) + return div(mul(exchangeAmount, quoteExchangeRate.toString()), '1', 2) } }, [ fiatInputDisabled, isMaxAmount, maxQuoteForMaxFlow, - userInput, + exchangeAmount, lastUsedInput, quoteExchangeRate ]) @@ -421,11 +421,11 @@ export const RampCreateScene: React.FC = (props: Props) => { if (isMaxAmount && maxQuoteForMaxFlow != null) { return maxQuoteForMaxFlow.cryptoAmount ?? '' } - if (userInput === '' || lastUsedInput === null) return '' + if (exchangeAmount === '' || lastUsedInput === null) return '' if (lastUsedInput === 'crypto') { // User entered crypto, show raw value (FilledTextInput will format it) - return userInput + return exchangeAmount } else { // Avoid division by zero if (quoteExchangeRate === 0) return '' @@ -434,13 +434,13 @@ export const RampCreateScene: React.FC = (props: Props) => { ? mulToPrecision(denomination.multiplier) : DECIMAL_PRECISION // User entered fiat, convert to crypto only if we have a quote - return div(userInput, quoteExchangeRate.toString(), decimals) + return div(exchangeAmount, quoteExchangeRate.toString(), decimals) } }, [ cryptoInputDisabled, isMaxAmount, maxQuoteForMaxFlow, - userInput, + exchangeAmount, lastUsedInput, quoteExchangeRate, denomination @@ -512,7 +512,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet == null || selectedCryptoCurrencyCode == null || lastUsedInput == null || - (userInput === '' && !isMaxAmount) || + (exchangeAmount === '' && !isMaxAmount) || rampQuoteRequest == null ) { return @@ -547,13 +547,13 @@ export const RampCreateScene: React.FC = (props: Props) => { const handleFiatChangeText = useHandler((text: string) => { setIsMaxAmount(false) - setUserInput(text) + setExchangeAmount(text) setLastUsedInput('fiat') }) const handleCryptoChangeText = useHandler((text: string) => { setIsMaxAmount(false) - setUserInput(text) + setExchangeAmount(text) setLastUsedInput('crypto') }) @@ -571,7 +571,7 @@ export const RampCreateScene: React.FC = (props: Props) => { setPendingMaxNav(true) setIsMaxAmount(true) setLastUsedInput('fiat') - setUserInput('') + setExchangeAmount('') }) // Auto-navigate once a best quote arrives for the transient max flow @@ -589,13 +589,13 @@ export const RampCreateScene: React.FC = (props: Props) => { // Persist the chosen amount so it remains after returning if (!fiatInputDisabled && maxQuoteForMaxFlow.fiatAmount != null) { setLastUsedInput('fiat') - setUserInput(maxQuoteForMaxFlow.fiatAmount) + setExchangeAmount(maxQuoteForMaxFlow.fiatAmount) } else if ( !cryptoInputDisabled && maxQuoteForMaxFlow.cryptoAmount != null ) { setLastUsedInput('crypto') - setUserInput(maxQuoteForMaxFlow.cryptoAmount) + setExchangeAmount(maxQuoteForMaxFlow.cryptoAmount) } navigation.navigate('rampSelectOption', { @@ -768,7 +768,7 @@ export const RampCreateScene: React.FC = (props: Props) => { {selectedCrypto == null || selectedWallet == null || denomination == null || - (userInput === '' && !isMaxAmount) || + (exchangeAmount === '' && !isMaxAmount) || lastUsedInput == null || (!isLoadingQuotes && sortedQuotes.length === 0) ? null : ( <> @@ -794,7 +794,7 @@ export const RampCreateScene: React.FC = (props: Props) => { sortedQuotes.length === 0 && quoteErrors.length === 0 && // User has queried - userInput !== '' && + exchangeAmount !== '' && lastUsedInput != null && selectedWallet != null && selectedCryptoCurrencyCode != null ? ( @@ -819,7 +819,7 @@ export const RampCreateScene: React.FC = (props: Props) => { {!isResultLoading && sortedQuotes.length === 0 && supportedPlugins.length > 0 && - (userInput !== '' || isMaxAmount) ? ( + (exchangeAmount !== '' || isMaxAmount) ? ( supportedPluginsError != null ? ( // Supported plugin error @@ -849,7 +849,7 @@ export const RampCreateScene: React.FC = (props: Props) => { isResultLoading || selectedWallet == null || selectedCryptoCurrencyCode == null || - (userInput === '' && !isMaxAmount) || + (exchangeAmount === '' && !isMaxAmount) || lastUsedInput === null || supportedPlugins.length === 0 || sortedQuotes.length === 0 || From ce709fb90a8c45e0ab04eba4fb35ff32e832aae8 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 16:47:55 -0700 Subject: [PATCH 5/8] Use `null` as the type for empty exchangeAmount input --- src/components/scenes/RampCreateScene.tsx | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index 8c78686e25d..907f8d9048c 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -102,7 +102,7 @@ export const RampCreateScene: React.FC = (props: Props) => { ) // State for trade form - const [exchangeAmount, setExchangeAmount] = useState('') + const [exchangeAmount, setExchangeAmount] = useState(null) const [lastUsedInput, setLastUsedInput] = useState<'fiat' | 'crypto' | null>( null ) @@ -246,7 +246,7 @@ export const RampCreateScene: React.FC = (props: Props) => { if ( hasAppliedInitialAmount.current || fiatInputDisabled || - exchangeAmount !== '' || + exchangeAmount != null || lastUsedInput != null || shouldShowRegionSelect ) { @@ -291,7 +291,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet == null || selectedCryptoCurrencyCode == null || lastUsedInput == null || - (exchangeAmount === '' && !isMaxAmount) || + (exchangeAmount == null && !isMaxAmount) || countryCode === '' ) { return null @@ -310,7 +310,7 @@ export const RampCreateScene: React.FC = (props: Props) => { pluginId: selectedWallet.currencyInfo.pluginId, tokenId: selectedCrypto?.tokenId ?? null, displayCurrencyCode: selectedCryptoCurrencyCode, - exchangeAmount: isMaxAmount ? { max: true } : exchangeAmount, + exchangeAmount: isMaxAmount ? { max: true } : exchangeAmount ?? '', fiatCurrencyCode: selectedFiatCurrencyCode, amountType: lastUsedInput, direction, @@ -394,7 +394,7 @@ export const RampCreateScene: React.FC = (props: Props) => { if (isMaxAmount && maxQuoteForMaxFlow != null) { return maxQuoteForMaxFlow.fiatAmount ?? '' } - if (exchangeAmount === '' || lastUsedInput === null) return '' + if (exchangeAmount == null || lastUsedInput === null) return '' if (lastUsedInput === 'fiat') { // User entered fiat, show raw value (FilledTextInput will format it) @@ -421,7 +421,7 @@ export const RampCreateScene: React.FC = (props: Props) => { if (isMaxAmount && maxQuoteForMaxFlow != null) { return maxQuoteForMaxFlow.cryptoAmount ?? '' } - if (exchangeAmount === '' || lastUsedInput === null) return '' + if (exchangeAmount == null || lastUsedInput === null) return '' if (lastUsedInput === 'crypto') { // User entered crypto, show raw value (FilledTextInput will format it) @@ -512,7 +512,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet == null || selectedCryptoCurrencyCode == null || lastUsedInput == null || - (exchangeAmount === '' && !isMaxAmount) || + (exchangeAmount == null && !isMaxAmount) || rampQuoteRequest == null ) { return @@ -547,13 +547,13 @@ export const RampCreateScene: React.FC = (props: Props) => { const handleFiatChangeText = useHandler((text: string) => { setIsMaxAmount(false) - setExchangeAmount(text) + setExchangeAmount(text === '' ? null : text) setLastUsedInput('fiat') }) const handleCryptoChangeText = useHandler((text: string) => { setIsMaxAmount(false) - setExchangeAmount(text) + setExchangeAmount(text === '' ? null : text) setLastUsedInput('crypto') }) @@ -571,7 +571,7 @@ export const RampCreateScene: React.FC = (props: Props) => { setPendingMaxNav(true) setIsMaxAmount(true) setLastUsedInput('fiat') - setExchangeAmount('') + setExchangeAmount(null) }) // Auto-navigate once a best quote arrives for the transient max flow @@ -768,7 +768,7 @@ export const RampCreateScene: React.FC = (props: Props) => { {selectedCrypto == null || selectedWallet == null || denomination == null || - (exchangeAmount === '' && !isMaxAmount) || + (exchangeAmount == null && !isMaxAmount) || lastUsedInput == null || (!isLoadingQuotes && sortedQuotes.length === 0) ? null : ( <> @@ -794,7 +794,7 @@ export const RampCreateScene: React.FC = (props: Props) => { sortedQuotes.length === 0 && quoteErrors.length === 0 && // User has queried - exchangeAmount !== '' && + exchangeAmount != null && lastUsedInput != null && selectedWallet != null && selectedCryptoCurrencyCode != null ? ( @@ -819,7 +819,7 @@ export const RampCreateScene: React.FC = (props: Props) => { {!isResultLoading && sortedQuotes.length === 0 && supportedPlugins.length > 0 && - (exchangeAmount !== '' || isMaxAmount) ? ( + (exchangeAmount != null || isMaxAmount) ? ( supportedPluginsError != null ? ( // Supported plugin error @@ -849,7 +849,7 @@ export const RampCreateScene: React.FC = (props: Props) => { isResultLoading || selectedWallet == null || selectedCryptoCurrencyCode == null || - (exchangeAmount === '' && !isMaxAmount) || + (exchangeAmount == null && !isMaxAmount) || lastUsedInput === null || supportedPlugins.length === 0 || sortedQuotes.length === 0 || From 1e877a733816efdc71dcb19713d620363ae0a4b1 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Tue, 21 Oct 2025 14:00:51 -0700 Subject: [PATCH 6/8] Implement new RampExchangeAmount interface in RampCreate --- src/components/scenes/RampCreateScene.tsx | 100 ++++++++++-------- src/plugins/ramps/banxa/banxaRampPlugin.ts | 4 +- src/plugins/ramps/bity/bityRampPlugin.ts | 4 +- .../ramps/moonpay/moonpayRampPlugin.ts | 4 +- src/plugins/ramps/paybis/paybisRampPlugin.ts | 4 +- src/plugins/ramps/rampPluginTypes.ts | 8 +- .../ramps/revolut/revolutRampPlugin.ts | 4 +- .../ramps/simplex/simplexRampPlugin.ts | 4 +- 8 files changed, 73 insertions(+), 59 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index 907f8d9048c..999b3f94a9b 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -26,6 +26,7 @@ import { import { useWatch } from '../../hooks/useWatch' import { lstrings } from '../../locales/strings' import type { + RampExchangeAmount, RampPlugin, RampQuote, RampQuoteRequest @@ -102,11 +103,12 @@ export const RampCreateScene: React.FC = (props: Props) => { ) // State for trade form - const [exchangeAmount, setExchangeAmount] = useState(null) + const [exchangeAmount, setExchangeAmount] = useState< + RampExchangeAmount | { empty: true } + >({ empty: true }) const [lastUsedInput, setLastUsedInput] = useState<'fiat' | 'crypto' | null>( null ) - const [isMaxAmount, setIsMaxAmount] = useState(false) const [pendingMaxNav, setPendingMaxNav] = useState(false) const hasAppliedInitialAmount = React.useRef(false) @@ -246,7 +248,7 @@ export const RampCreateScene: React.FC = (props: Props) => { if ( hasAppliedInitialAmount.current || fiatInputDisabled || - exchangeAmount != null || + 'empty' in exchangeAmount || lastUsedInput != null || shouldShowRegionSelect ) { @@ -265,7 +267,7 @@ export const RampCreateScene: React.FC = (props: Props) => { ) hasAppliedInitialAmount.current = true - setExchangeAmount(initialFiat) + setExchangeAmount({ amount: initialFiat }) setLastUsedInput('fiat') } @@ -291,7 +293,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet == null || selectedCryptoCurrencyCode == null || lastUsedInput == null || - (exchangeAmount == null && !isMaxAmount) || + 'empty' in exchangeAmount || countryCode === '' ) { return null @@ -310,7 +312,7 @@ export const RampCreateScene: React.FC = (props: Props) => { pluginId: selectedWallet.currencyInfo.pluginId, tokenId: selectedCrypto?.tokenId ?? null, displayCurrencyCode: selectedCryptoCurrencyCode, - exchangeAmount: isMaxAmount ? { max: true } : exchangeAmount ?? '', + exchangeAmount, fiatCurrencyCode: selectedFiatCurrencyCode, amountType: lastUsedInput, direction, @@ -324,7 +326,6 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedCryptoCurrencyCode, selectedCrypto, exchangeAmount, - isMaxAmount, selectedFiatCurrencyCode, lastUsedInput, countryCode, @@ -352,7 +353,7 @@ export const RampCreateScene: React.FC = (props: Props) => { // For Max flow, select the quote with the largest supported amount const maxQuoteForMaxFlow = React.useMemo(() => { - if (!isMaxAmount || sortedQuotes.length === 0) return null + if (!('max' in exchangeAmount) || sortedQuotes.length === 0) return null const picked = sortedQuotes.reduce((a, b): RampQuote => { const aAmount = lastUsedInput === 'crypto' ? a.cryptoAmount : a.fiatAmount @@ -360,7 +361,7 @@ export const RampCreateScene: React.FC = (props: Props) => { return gt(bAmount, aAmount) ? b : a }) return picked - }, [isMaxAmount, sortedQuotes, lastUsedInput]) + }, [exchangeAmount, sortedQuotes, lastUsedInput]) // Calculate exchange rate from best quote const quoteExchangeRate = React.useMemo(() => { @@ -390,24 +391,32 @@ export const RampCreateScene: React.FC = (props: Props) => { const displayFiatAmount = React.useMemo(() => { // Don't show any value if fiat input is disabled if (fiatInputDisabled) return '' + if ('empty' in exchangeAmount) return '' - if (isMaxAmount && maxQuoteForMaxFlow != null) { - return maxQuoteForMaxFlow.fiatAmount ?? '' + if ('max' in exchangeAmount) { + if (maxQuoteForMaxFlow != null) { + return maxQuoteForMaxFlow.fiatAmount ?? '' + } + return '' } - if (exchangeAmount == null || lastUsedInput === null) return '' if (lastUsedInput === 'fiat') { // User entered fiat, show raw value (FilledTextInput will format it) - return exchangeAmount - } else { + return exchangeAmount.amount + } else if (lastUsedInput === 'crypto') { // Avoid division by zero if (quoteExchangeRate === 0) return '' // User entered crypto, convert to fiat only if we have a quote - return div(mul(exchangeAmount, quoteExchangeRate.toString()), '1', 2) + return div( + mul(exchangeAmount.amount, quoteExchangeRate.toString()), + '1', + 2 + ) + } else { + return '' } }, [ fiatInputDisabled, - isMaxAmount, maxQuoteForMaxFlow, exchangeAmount, lastUsedInput, @@ -417,16 +426,19 @@ export const RampCreateScene: React.FC = (props: Props) => { const displayCryptoAmount = React.useMemo(() => { // Don't show any value if crypto input is disabled if (cryptoInputDisabled) return '' + if ('empty' in exchangeAmount || lastUsedInput === null) return '' - if (isMaxAmount && maxQuoteForMaxFlow != null) { - return maxQuoteForMaxFlow.cryptoAmount ?? '' + if ('max' in exchangeAmount) { + if (maxQuoteForMaxFlow != null) { + return maxQuoteForMaxFlow.cryptoAmount ?? '' + } + return '' } - if (exchangeAmount == null || lastUsedInput === null) return '' if (lastUsedInput === 'crypto') { // User entered crypto, show raw value (FilledTextInput will format it) - return exchangeAmount - } else { + return exchangeAmount.amount + } else if (lastUsedInput === 'fiat') { // Avoid division by zero if (quoteExchangeRate === 0) return '' const decimals = @@ -434,11 +446,12 @@ export const RampCreateScene: React.FC = (props: Props) => { ? mulToPrecision(denomination.multiplier) : DECIMAL_PRECISION // User entered fiat, convert to crypto only if we have a quote - return div(exchangeAmount, quoteExchangeRate.toString(), decimals) + return div(exchangeAmount.amount, quoteExchangeRate.toString(), decimals) + } else { + return '' } }, [ cryptoInputDisabled, - isMaxAmount, maxQuoteForMaxFlow, exchangeAmount, lastUsedInput, @@ -512,7 +525,7 @@ export const RampCreateScene: React.FC = (props: Props) => { selectedWallet == null || selectedCryptoCurrencyCode == null || lastUsedInput == null || - (exchangeAmount == null && !isMaxAmount) || + 'empty' in exchangeAmount || rampQuoteRequest == null ) { return @@ -545,15 +558,13 @@ export const RampCreateScene: React.FC = (props: Props) => { return getRateFromRampQuoteResult(bestQuote, selectedFiatCurrencyCode) }, [bestQuote, selectedFiatCurrencyCode]) - const handleFiatChangeText = useHandler((text: string) => { - setIsMaxAmount(false) - setExchangeAmount(text === '' ? null : text) + const handleFiatChangeText = useHandler((amount: string) => { + setExchangeAmount(amount === '' ? { empty: true } : { amount }) setLastUsedInput('fiat') }) - const handleCryptoChangeText = useHandler((text: string) => { - setIsMaxAmount(false) - setExchangeAmount(text === '' ? null : text) + const handleCryptoChangeText = useHandler((amount: string) => { + setExchangeAmount(amount === '' ? { empty: true } : { amount }) setLastUsedInput('crypto') }) @@ -569,19 +580,17 @@ export const RampCreateScene: React.FC = (props: Props) => { // Trigger a transient max flow: request quotes with {max:true} and auto-navigate when ready setPendingMaxNav(true) - setIsMaxAmount(true) setLastUsedInput('fiat') - setExchangeAmount(null) + setExchangeAmount({ max: true }) }) // Auto-navigate once a best quote arrives for the transient max flow React.useEffect(() => { const isMaxRequest = - rampQuoteRequest != null && - typeof rampQuoteRequest.exchangeAmount !== 'string' + rampQuoteRequest != null && 'max' in rampQuoteRequest.exchangeAmount if ( pendingMaxNav && - isMaxAmount && + 'max' in exchangeAmount && isMaxRequest && maxQuoteForMaxFlow != null && !isLoadingQuotes @@ -589,13 +598,13 @@ export const RampCreateScene: React.FC = (props: Props) => { // Persist the chosen amount so it remains after returning if (!fiatInputDisabled && maxQuoteForMaxFlow.fiatAmount != null) { setLastUsedInput('fiat') - setExchangeAmount(maxQuoteForMaxFlow.fiatAmount) + setExchangeAmount({ amount: maxQuoteForMaxFlow.fiatAmount }) } else if ( !cryptoInputDisabled && maxQuoteForMaxFlow.cryptoAmount != null ) { setLastUsedInput('crypto') - setExchangeAmount(maxQuoteForMaxFlow.cryptoAmount) + setExchangeAmount({ amount: maxQuoteForMaxFlow.cryptoAmount }) } navigation.navigate('rampSelectOption', { @@ -603,17 +612,16 @@ export const RampCreateScene: React.FC = (props: Props) => { }) // Reset transient state to avoid leaving the scene in a max "mode" setPendingMaxNav(false) - setIsMaxAmount(false) } }, [ pendingMaxNav, - isMaxAmount, maxQuoteForMaxFlow, isLoadingQuotes, rampQuoteRequest, navigation, fiatInputDisabled, - cryptoInputDisabled + cryptoInputDisabled, + exchangeAmount ]) const headerTitle = @@ -689,7 +697,7 @@ export const RampCreateScene: React.FC = (props: Props) => { maxDecimals={2} returnKeyType="done" showSpinner={isFetchingQuotes && lastUsedInput === 'crypto'} - disabled={isMaxAmount || fiatInputDisabled} + disabled={'max' in exchangeAmount || fiatInputDisabled} expand /> @@ -736,7 +744,7 @@ export const RampCreateScene: React.FC = (props: Props) => { showSpinner={isFetchingQuotes && lastUsedInput === 'fiat'} disabled={ isLoadingPersistedCryptoSelection || - isMaxAmount || + 'max' in exchangeAmount || cryptoInputDisabled } expand @@ -768,7 +776,7 @@ export const RampCreateScene: React.FC = (props: Props) => { {selectedCrypto == null || selectedWallet == null || denomination == null || - (exchangeAmount == null && !isMaxAmount) || + 'empty' in exchangeAmount || lastUsedInput == null || (!isLoadingQuotes && sortedQuotes.length === 0) ? null : ( <> @@ -794,7 +802,7 @@ export const RampCreateScene: React.FC = (props: Props) => { sortedQuotes.length === 0 && quoteErrors.length === 0 && // User has queried - exchangeAmount != null && + !('empty' in exchangeAmount) && lastUsedInput != null && selectedWallet != null && selectedCryptoCurrencyCode != null ? ( @@ -819,7 +827,7 @@ export const RampCreateScene: React.FC = (props: Props) => { {!isResultLoading && sortedQuotes.length === 0 && supportedPlugins.length > 0 && - (exchangeAmount != null || isMaxAmount) ? ( + !('empty' in exchangeAmount) ? ( supportedPluginsError != null ? ( // Supported plugin error @@ -849,7 +857,7 @@ export const RampCreateScene: React.FC = (props: Props) => { isResultLoading || selectedWallet == null || selectedCryptoCurrencyCode == null || - (exchangeAmount == null && !isMaxAmount) || + 'empty' in exchangeAmount || lastUsedInput === null || supportedPlugins.length === 0 || sortedQuotes.length === 0 || diff --git a/src/plugins/ramps/banxa/banxaRampPlugin.ts b/src/plugins/ramps/banxa/banxaRampPlugin.ts index 4d23c361289..ce0c018e912 100644 --- a/src/plugins/ramps/banxa/banxaRampPlugin.ts +++ b/src/plugins/ramps/banxa/banxaRampPlugin.ts @@ -891,9 +891,9 @@ export const banxaRampPlugin: RampPluginFactory = ( const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = - typeof request.exchangeAmount === 'object' && request.exchangeAmount.max + 'max' in request.exchangeAmount && request.exchangeAmount.max const exchangeAmount = - typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + 'amount' in request.exchangeAmount ? request.exchangeAmount.amount : '' // Fetch provider configuration (cached or fresh) const config = await fetchProviderConfig() diff --git a/src/plugins/ramps/bity/bityRampPlugin.ts b/src/plugins/ramps/bity/bityRampPlugin.ts index cd829e0aa5c..b856801f5b0 100644 --- a/src/plugins/ramps/bity/bityRampPlugin.ts +++ b/src/plugins/ramps/bity/bityRampPlugin.ts @@ -686,9 +686,9 @@ export const bityRampPlugin = (pluginConfig: RampPluginConfig): RampPlugin => { const isBuy = direction === 'buy' const isMaxAmount = - typeof request.exchangeAmount === 'object' && request.exchangeAmount.max + 'max' in request.exchangeAmount && request.exchangeAmount.max const exchangeAmount = - typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + 'amount' in request.exchangeAmount ? request.exchangeAmount.amount : '' // Validate region using helper function if (!isRegionSupported(regionCode)) { diff --git a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts index 0f230cade39..0ec8f065db5 100644 --- a/src/plugins/ramps/moonpay/moonpayRampPlugin.ts +++ b/src/plugins/ramps/moonpay/moonpayRampPlugin.ts @@ -518,9 +518,9 @@ export const moonpayRampPlugin: RampPluginFactory = ( const fiatCurrencyCode = ensureIsoPrefix(request.fiatCurrencyCode) const isMaxAmount = - typeof request.exchangeAmount === 'object' && request.exchangeAmount.max + 'max' in request.exchangeAmount && request.exchangeAmount.max const exchangeAmountString = - typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + 'amount' in request.exchangeAmount ? request.exchangeAmount.amount : '' // Fetch provider configuration (with caching) const config = await fetchProviderConfig() diff --git a/src/plugins/ramps/paybis/paybisRampPlugin.ts b/src/plugins/ramps/paybis/paybisRampPlugin.ts index 5b644ec068c..429b8bae0e3 100644 --- a/src/plugins/ramps/paybis/paybisRampPlugin.ts +++ b/src/plugins/ramps/paybis/paybisRampPlugin.ts @@ -748,9 +748,9 @@ export const paybisRampPlugin: RampPluginFactory = ( const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = - typeof request.exchangeAmount === 'object' && request.exchangeAmount.max + 'max' in request.exchangeAmount && request.exchangeAmount.max const exchangeAmount = - typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + 'amount' in request.exchangeAmount ? request.exchangeAmount.amount : '' // Validate region restrictions if (regionCode != null) { diff --git a/src/plugins/ramps/rampPluginTypes.ts b/src/plugins/ramps/rampPluginTypes.ts index 138da33e9d4..0e7e6539273 100644 --- a/src/plugins/ramps/rampPluginTypes.ts +++ b/src/plugins/ramps/rampPluginTypes.ts @@ -37,11 +37,17 @@ export interface RampSupportResult { supportedAmountTypes?: Array<'fiat' | 'crypto'> } +export type RampExchangeAmount = + | { + max: true + } + | { amount: string } + export interface RampQuoteRequest { wallet: EdgeCurrencyWallet tokenId: EdgeTokenId displayCurrencyCode: string - exchangeAmount: string | { max: true } + exchangeAmount: RampExchangeAmount fiatCurrencyCode: string amountType: 'fiat' | 'crypto' direction: 'buy' | 'sell' diff --git a/src/plugins/ramps/revolut/revolutRampPlugin.ts b/src/plugins/ramps/revolut/revolutRampPlugin.ts index 999573c9a21..9a7992bb71f 100644 --- a/src/plugins/ramps/revolut/revolutRampPlugin.ts +++ b/src/plugins/ramps/revolut/revolutRampPlugin.ts @@ -192,9 +192,9 @@ export const revolutRampPlugin: RampPluginFactory = ( const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = - typeof request.exchangeAmount === 'object' && request.exchangeAmount.max + 'max' in request.exchangeAmount && request.exchangeAmount.max const exchangeAmount = - typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + 'amount' in request.exchangeAmount ? request.exchangeAmount.amount : '' // Constraints per request const constraintOk = validateRampQuoteRequest( diff --git a/src/plugins/ramps/simplex/simplexRampPlugin.ts b/src/plugins/ramps/simplex/simplexRampPlugin.ts index dede88275e4..824ada37ec9 100644 --- a/src/plugins/ramps/simplex/simplexRampPlugin.ts +++ b/src/plugins/ramps/simplex/simplexRampPlugin.ts @@ -481,9 +481,9 @@ export const simplexRampPlugin: RampPluginFactory = ( const currencyPluginId = request.wallet.currencyInfo.pluginId const isMaxAmount = - typeof request.exchangeAmount === 'object' && request.exchangeAmount.max + 'max' in request.exchangeAmount && request.exchangeAmount.max const exchangeAmount = - typeof request.exchangeAmount === 'object' ? '' : request.exchangeAmount + 'amount' in request.exchangeAmount ? request.exchangeAmount.amount : '' // Validate direction if (!validateDirection(direction)) { From f4ca4a4dc504104e27058cbe3aa5eb8a5917c36a Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 17:19:37 -0700 Subject: [PATCH 7/8] Decouple input disable logic from amount type support --- src/components/scenes/RampCreateScene.tsx | 71 ++++++++++++----------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index 999b3f94a9b..aebd8e23a25 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -79,12 +79,6 @@ type Props = ( direction: 'buy' | 'sell' } -// Helper function to determine which input types should be disabled -interface AmountTypeSupport { - fiatInputDisabled: boolean - cryptoInputDisabled: boolean -} - export const RampCreateScene: React.FC = (props: Props) => { const { direction, navigation, route } = props const { regionCode: initialRegionCode, forcedWalletResult } = @@ -220,8 +214,7 @@ export const RampCreateScene: React.FC = (props: Props) => { }, [selectedFiatCurrencyCode]) // Determine which input types should be disabled - const { fiatInputDisabled, cryptoInputDisabled } = - getAmountTypeSupport(supportedPlugins) + const amountTypeSupport = getAmountTypeSupport(supportedPlugins) const { data: fiatUsdRate } = useQuery({ queryKey: ['fiatUsdRate', selectedFiatCurrencyCode], @@ -247,7 +240,7 @@ export const RampCreateScene: React.FC = (props: Props) => { // Don't override if the user has started typing or fiat input is disabled if ( hasAppliedInitialAmount.current || - fiatInputDisabled || + amountTypeSupport.onlyCrypto || 'empty' in exchangeAmount || lastUsedInput != null || shouldShowRegionSelect @@ -276,7 +269,7 @@ export const RampCreateScene: React.FC = (props: Props) => { abort = true } }, [ - fiatInputDisabled, + amountTypeSupport.onlyCrypto, isLightAccount, lastUsedInput, selectedWallet, @@ -301,8 +294,8 @@ export const RampCreateScene: React.FC = (props: Props) => { // Guard against creating request with disabled input type if ( - (lastUsedInput === 'fiat' && fiatInputDisabled) || - (lastUsedInput === 'crypto' && cryptoInputDisabled) + (lastUsedInput === 'fiat' && amountTypeSupport.onlyCrypto) || + (lastUsedInput === 'crypto' && amountTypeSupport.onlyFiat) ) { return null } @@ -330,8 +323,8 @@ export const RampCreateScene: React.FC = (props: Props) => { lastUsedInput, countryCode, stateProvinceCode, - fiatInputDisabled, - cryptoInputDisabled, + amountTypeSupport.onlyCrypto, + amountTypeSupport.onlyFiat, direction ]) @@ -390,7 +383,7 @@ export const RampCreateScene: React.FC = (props: Props) => { // Derived state for display values const displayFiatAmount = React.useMemo(() => { // Don't show any value if fiat input is disabled - if (fiatInputDisabled) return '' + if (amountTypeSupport.onlyCrypto) return '' if ('empty' in exchangeAmount) return '' if ('max' in exchangeAmount) { @@ -416,7 +409,7 @@ export const RampCreateScene: React.FC = (props: Props) => { return '' } }, [ - fiatInputDisabled, + amountTypeSupport.onlyCrypto, maxQuoteForMaxFlow, exchangeAmount, lastUsedInput, @@ -425,7 +418,7 @@ export const RampCreateScene: React.FC = (props: Props) => { const displayCryptoAmount = React.useMemo(() => { // Don't show any value if crypto input is disabled - if (cryptoInputDisabled) return '' + if (amountTypeSupport.onlyFiat) return '' if ('empty' in exchangeAmount || lastUsedInput === null) return '' if ('max' in exchangeAmount) { @@ -451,7 +444,7 @@ export const RampCreateScene: React.FC = (props: Props) => { return '' } }, [ - cryptoInputDisabled, + amountTypeSupport.onlyFiat, maxQuoteForMaxFlow, exchangeAmount, lastUsedInput, @@ -596,11 +589,14 @@ export const RampCreateScene: React.FC = (props: Props) => { !isLoadingQuotes ) { // Persist the chosen amount so it remains after returning - if (!fiatInputDisabled && maxQuoteForMaxFlow.fiatAmount != null) { + if ( + !amountTypeSupport.onlyCrypto && + maxQuoteForMaxFlow.fiatAmount != null + ) { setLastUsedInput('fiat') setExchangeAmount({ amount: maxQuoteForMaxFlow.fiatAmount }) } else if ( - !cryptoInputDisabled && + !amountTypeSupport.onlyFiat && maxQuoteForMaxFlow.cryptoAmount != null ) { setLastUsedInput('crypto') @@ -619,8 +615,8 @@ export const RampCreateScene: React.FC = (props: Props) => { isLoadingQuotes, rampQuoteRequest, navigation, - fiatInputDisabled, - cryptoInputDisabled, + amountTypeSupport.onlyCrypto, + amountTypeSupport.onlyFiat, exchangeAmount ]) @@ -641,6 +637,13 @@ export const RampCreateScene: React.FC = (props: Props) => { ) } + const fiatInputDisabled = + 'max' in exchangeAmount || amountTypeSupport.onlyCrypto + const cryptoInputDisabled = + isLoadingPersistedCryptoSelection || + 'max' in exchangeAmount || + amountTypeSupport.onlyFiat + // Render trade form view return ( <> @@ -697,7 +700,7 @@ export const RampCreateScene: React.FC = (props: Props) => { maxDecimals={2} returnKeyType="done" showSpinner={isFetchingQuotes && lastUsedInput === 'crypto'} - disabled={'max' in exchangeAmount || fiatInputDisabled} + disabled={fiatInputDisabled} expand /> @@ -742,11 +745,7 @@ export const RampCreateScene: React.FC = (props: Props) => { maxDecimals={6} returnKeyType="done" showSpinner={isFetchingQuotes && lastUsedInput === 'fiat'} - disabled={ - isLoadingPersistedCryptoSelection || - 'max' in exchangeAmount || - cryptoInputDisabled - } + disabled={cryptoInputDisabled} expand /> @@ -861,8 +860,8 @@ export const RampCreateScene: React.FC = (props: Props) => { lastUsedInput === null || supportedPlugins.length === 0 || sortedQuotes.length === 0 || - (lastUsedInput === 'fiat' && fiatInputDisabled) || - (lastUsedInput === 'crypto' && cryptoInputDisabled) + (lastUsedInput === 'fiat' && amountTypeSupport.onlyCrypto) || + (lastUsedInput === 'crypto' && amountTypeSupport.onlyFiat) } /> @@ -943,11 +942,17 @@ const getStyles = cacheStyles((theme: ReturnType) => ({ } })) +// Helper function to determine which input types should be disabled +interface AmountTypeSupport { + onlyCrypto: boolean + onlyFiat: boolean +} + function getAmountTypeSupport( supportedPlugins: SupportedPluginResult[] ): AmountTypeSupport { if (supportedPlugins.length === 0) { - return { fiatInputDisabled: false, cryptoInputDisabled: false } + return { onlyCrypto: false, onlyFiat: false } } // Collect all supported amount types from all plugins @@ -973,8 +978,8 @@ function getAmountTypeSupport( allSupportedTypes.has('crypto') && !allSupportedTypes.has('fiat') return { - fiatInputDisabled: onlyCrypto, - cryptoInputDisabled: onlyFiat + onlyCrypto, + onlyFiat } } From 5fc1483e85fa3decb668e9493a91fbffec3b0cc0 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Thu, 16 Oct 2025 17:22:22 -0700 Subject: [PATCH 8/8] Fix disabled amount inputs when no available quotes This is a bug. The amount inputs should not remain disabled when max quote requests return no quote results. --- src/components/scenes/RampCreateScene.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/scenes/RampCreateScene.tsx b/src/components/scenes/RampCreateScene.tsx index aebd8e23a25..628e095ac66 100644 --- a/src/components/scenes/RampCreateScene.tsx +++ b/src/components/scenes/RampCreateScene.tsx @@ -638,10 +638,11 @@ export const RampCreateScene: React.FC = (props: Props) => { } const fiatInputDisabled = - 'max' in exchangeAmount || amountTypeSupport.onlyCrypto + ('max' in exchangeAmount && sortedQuotes.length > 0) || + amountTypeSupport.onlyCrypto const cryptoInputDisabled = isLoadingPersistedCryptoSelection || - 'max' in exchangeAmount || + ('max' in exchangeAmount && sortedQuotes.length > 0) || amountTypeSupport.onlyFiat // Render trade form view