import { RouteProp } from '@react-navigation/native' import { NativeStackScreenProps } from '@react-navigation/native-stack' import BigNumber from 'bignumber.js' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import { Platform, StyleSheet, Text, TextInput, View } from 'react-native' import { getNumberFormatSettings } from 'react-native-localize' import { SafeAreaView } from 'react-native-safe-area-context' import { showError } from 'src/alert/actions' import AppAnalytics from 'src/analytics/AppAnalytics' import { FiatExchangeEvents } from 'src/analytics/Events' import { ErrorMessages } from 'src/app/ErrorMessages' import BackButton from 'src/components/BackButton' import Button, { BtnSizes, BtnTypes } from 'src/components/Button' import Dialog from 'src/components/Dialog' import KeyboardAwareScrollView from 'src/components/KeyboardAwareScrollView' import KeyboardSpacer from 'src/components/KeyboardSpacer' import { ALERT_BANNER_DURATION, DOLLAR_ADD_FUNDS_MAX_AMOUNT } from 'src/config' import { convertToFiatConnectFiatCurrency } from 'src/fiatconnect' import { attemptReturnUserFlowLoadingSelector, cachedFiatAccountUsesSelector, } from 'src/fiatconnect/selectors' import { attemptReturnUserFlow } from 'src/fiatconnect/slice' import { CICOFlow } from 'src/fiatExchanges/types' import { isUserInputCrypto } from 'src/fiatExchanges/utils' import i18n from 'src/i18n' import { LocalCurrencyCode, LocalCurrencySymbol } from 'src/localCurrency/consts' import { useLocalCurrencyCode } from 'src/localCurrency/hooks' import { usdToLocalCurrencyRateSelector } from 'src/localCurrency/selectors' import { HeaderTitleWithTokenBalance, emptyHeader } from 'src/navigator/Headers' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import { StackParamList } from 'src/navigator/types' import { useDispatch, useSelector } from 'src/redux/hooks' import DisconnectBanner from 'src/shared/DisconnectBanner' import colors from 'src/styles/colors' import { typeScale } from 'src/styles/fonts' import variables from 'src/styles/variables' import { useLocalToTokenAmount, useTokenInfo, useTokenToLocalAmount } from 'src/tokens/hooks' import { tokenSymbolToAnalyticsCurrency } from 'src/utils/currencies' import { roundUp } from 'src/utils/formatting' import { parseInputAmount } from 'src/utils/parsing' import networkConfig from 'src/web3/networkConfig' const { decimalSeparator } = getNumberFormatSettings() type RouteProps = NativeStackScreenProps type Props = RouteProps function FiatExchangeAmount({ route }: Props) { const { t } = useTranslation() const { flow, tokenId, tokenSymbol } = route.params const [showingInvalidAmountDialog, setShowingInvalidAmountDialog] = useState(false) const closeInvalidAmountDialog = () => { setShowingInvalidAmountDialog(false) } const [inputAmount, setInputAmount] = useState('') const parsedInputAmount = parseInputAmount(inputAmount, decimalSeparator) const tokenInfo = useTokenInfo(tokenId) const inputConvertedToCrypto = useLocalToTokenAmount(parsedInputAmount, tokenId) || new BigNumber(0) const inputConvertedToLocalCurrency = useTokenToLocalAmount(parsedInputAmount, tokenId) || new BigNumber(0) const localCurrencyCode = useLocalCurrencyCode() const usdToLocalRate = useSelector(usdToLocalCurrencyRateSelector) const cachedFiatAccountUses = useSelector(cachedFiatAccountUsesSelector) const attemptReturnUserFlowLoading = useSelector(attemptReturnUserFlowLoadingSelector) const localCurrencySymbol = LocalCurrencySymbol[localCurrencyCode] const inputIsCrypto = isUserInputCrypto(flow) const inputCryptoAmount = inputIsCrypto ? parsedInputAmount : inputConvertedToCrypto const inputLocalCurrencyAmount = inputIsCrypto ? inputConvertedToLocalCurrency : parsedInputAmount const maxWithdrawAmount = new BigNumber(tokenInfo?.balance ?? 0) const inputSymbol = inputIsCrypto ? '' : localCurrencySymbol const cUSDToken = useTokenInfo(networkConfig.cusdTokenId)! const localCurrencyMaxAmount = useTokenToLocalAmount(new BigNumber(DOLLAR_ADD_FUNDS_MAX_AMOUNT), cUSDToken.tokenId) || new BigNumber(0) let overLocalLimitDisplayString = '' if (localCurrencyCode !== LocalCurrencyCode.USD) { overLocalLimitDisplayString = ` (${localCurrencySymbol}${roundUp(localCurrencyMaxAmount)})` } const dispatch = useDispatch() function isNextButtonValid() { return !!tokenInfo && parsedInputAmount.isGreaterThan(0) } function onChangeExchangeAmount(amount: string) { setInputAmount(amount.replace(localCurrencySymbol, '')) } function goToProvidersScreen() { AppAnalytics.track(FiatExchangeEvents.cico_amount_chosen, { amount: inputCryptoAmount.toNumber(), currency: tokenSymbolToAnalyticsCurrency(tokenSymbol), flow, }) const amount = { crypto: inputCryptoAmount.toNumber(), // Rounding up to avoid decimal errors from providers. Won't be // necessary once we support inputting an amount in both crypto and fiat fiat: Math.round(inputLocalCurrencyAmount.toNumber()), } const previousFiatAccount = cachedFiatAccountUses.find( (account) => account.cryptoType === tokenSymbol && account.fiatType === convertToFiatConnectFiatCurrency(localCurrencyCode) ) if (previousFiatAccount) { // This will attempt to navigate to the Review Screen if the proper quote and fiatAccount are found // If not, then the user will be navigated to the SelectProvider screen as normal const { providerId, fiatAccountId, fiatAccountType, fiatAccountSchema } = previousFiatAccount dispatch( attemptReturnUserFlow({ flow, selectedCrypto: tokenSymbol, amount, providerId, fiatAccountId, fiatAccountType, fiatAccountSchema, tokenId, }) ) } else { navigate(Screens.SelectProvider, { flow, tokenId, amount, }) } } function onPressContinue() { if (flow === CICOFlow.CashIn) { if (inputLocalCurrencyAmount.isGreaterThan(localCurrencyMaxAmount)) { setShowingInvalidAmountDialog(true) AppAnalytics.track(FiatExchangeEvents.cico_amount_chosen_invalid, { amount: inputCryptoAmount.toNumber(), currency: tokenSymbolToAnalyticsCurrency(tokenSymbol), flow, }) return } } else if (inputCryptoAmount.isGreaterThan(maxWithdrawAmount)) { dispatch( showError(ErrorMessages.CASH_OUT_LIMIT_EXCEEDED, ALERT_BANNER_DURATION, { balance: maxWithdrawAmount.toFixed(2), currency: tokenSymbol, }) ) return } goToProvidersScreen() } return ( {t('invalidAmountDialog.maxAmount', { usdLimit: `$${DOLLAR_ADD_FUNDS_MAX_AMOUNT}`, localLimit: overLocalLimitDisplayString, })} {inputIsCrypto ? `${t('amount')} (${tokenSymbol})` : t('amount')} 0 ? `${inputSymbol}${inputAmount}` : undefined} placeholderTextColor={colors.inactive} placeholder={`${inputSymbol}0`} style={[styles.currencyInput, styles.fiatCurrencyColor]} testID="FiatExchangeInput" />