From 25455e69d003352abf9636dde27abf3aac5315f5 Mon Sep 17 00:00:00 2001 From: Sergey Chystiakov Date: Mon, 3 Jun 2024 08:41:59 +0200 Subject: [PATCH] fix: amount field and max button --- src/components/basic/Input/Input.js | 2 +- src/components/composed/Balance/Balance.js | 4 +- .../composed/CryptoField/CryptoField.css | 81 ++++++++ .../composed/CryptoField/CryptoField.js | 146 ++++++++++++++ .../CryptoField/CryptoField.stories.js | 103 ++++++++++ .../composed/CryptoField/CryptoField.test.js | 181 ++++++++++++++++++ src/components/composed/FeeField/FeeField.css | 2 +- .../composed/FeeField/FeeFieldML.js | 7 - src/components/composed/index.js | 2 + .../containers/SendTransaction/AmountField.js | 8 +- .../SendTransaction/SendTransaction.js | 17 +- .../SendTransactionConfirmation.js | 4 +- src/pages/DelegationStake/DelegationStake.js | 7 +- src/utils/Helpers/ML/MLTransaction.js | 1 + src/utils/Helpers/Number/Format.js | 7 +- 15 files changed, 546 insertions(+), 26 deletions(-) create mode 100644 src/components/composed/CryptoField/CryptoField.css create mode 100644 src/components/composed/CryptoField/CryptoField.js create mode 100644 src/components/composed/CryptoField/CryptoField.stories.js create mode 100644 src/components/composed/CryptoField/CryptoField.test.js diff --git a/src/components/basic/Input/Input.js b/src/components/basic/Input/Input.js index 4955f834..aae7b4e6 100644 --- a/src/components/basic/Input/Input.js +++ b/src/components/basic/Input/Input.js @@ -69,7 +69,7 @@ const Input = ({ target: { value }, } = ev - if (justNumbers && value.match(/[^0-9,.]/)) return false + if (justNumbers && value.match(/[^0-9.]/)) return false let newValue = password ? value : value.toString().trim() if (mask && getMaskedValue) { diff --git a/src/components/composed/Balance/Balance.js b/src/components/composed/Balance/Balance.js index 77a86d91..7aacf4e1 100644 --- a/src/components/composed/Balance/Balance.js +++ b/src/components/composed/Balance/Balance.js @@ -28,6 +28,8 @@ const Balance = ({ balance, balanceLocked, exchangeRate, walletType }) => { return tokenBalances[walletType.name].token_info.token_ticker.string } + const cryptoDecimals = walletType.name === 'Bitcoin' ? 8 : 11 + const logo = () => { if (walletType.name === 'Mintlayer') { return @@ -55,7 +57,7 @@ const Balance = ({ balance, balanceLocked, exchangeRate, walletType }) => { className="balance-btc" data-testid="balance-paragraph" > - {Format.BTCValue(balance)} {symbol()} + {Format.BTCValue(balance, cryptoDecimals)} {symbol()}

{ + const { walletType } = useContext(AccountContext) + const { networkType } = useContext(SettingsContext) + const { transactionMode } = useContext(TransactionContext) + const maxValue = NumbersHelper.floatStringToNumber(maxValueInToken) + + // eslint-disable-next-line no-unused-vars + const [currentValueType, setCurrentValueType] = useState( + transactionData ? transactionData.tokenName : 'Token', + ) + const isDelegationMode = + transactionMode === AppInfo.ML_TRANSACTION_MODES.DELEGATION && + walletType.name === 'Mintlayer' + + if (!transactionData) return null + + console.log('inputValue < maxValue', value, maxValue) + console.log(value < maxValueInToken) + + const { tokenName, fiatName } = transactionData + const inputExtraClasses = ['crypto-fiat-input'] + + const isTypeFiat = () => currentValueType === fiatName + + + const calculateFiatValue = (value) => { + if (!value) { + return Format.fiatValue(0) + } + const parsedValue = NumbersHelper.floatStringToNumber(value) + return Format.fiatValue(parsedValue * exchangeRate) + } + + const bottomValue = calculateFiatValue(value) + + const formattedBottomValue = `≈ ${ + bottomValue ? bottomValue : Format.fiatValue(0) + } ${isTypeFiat() ? tokenName : fiatName}` + + // Consider the correct format for 0,00 that might also be 0.00 + const displayedBottomValue = + networkType === AppInfo.NETWORK_TYPES.TESTNET + ? '' + : formattedBottomValue + + const maxButtonClickHandler = () => { + console.log('maxValue', maxValue) + changeValueHandle({ + currency: currentValueType, + value: maxValue, + }) + } + + const changeHandler = ({ target: { value } }) => { + // remove non-numeric characters from the input without 3rd party libraries + const numericValue = value.replace(/[^0-9.]/g, '') + + if (value !== numericValue) { + return + } + + changeValueHandle({ + currency: currentValueType, + value: numericValue, + }) + } + + return ( +

+
+ + + +
+ + + + { + displayedBottomValue && ( +

+ {displayedBottomValue} +

+ ) + } +
+ ) +} + +export default CryptoField diff --git a/src/components/composed/CryptoField/CryptoField.stories.js b/src/components/composed/CryptoField/CryptoField.stories.js new file mode 100644 index 00000000..f7873ab3 --- /dev/null +++ b/src/components/composed/CryptoField/CryptoField.stories.js @@ -0,0 +1,103 @@ +import React from 'react' +import CryptoFiatField from './CryptoFiatField' + +export default { + title: 'Components/Composed/CryptoFiatField', + component: CryptoFiatField, + parameters: { + layout: 'centered', + }, + args: { + placeholder: 'Placeholder', + buttonTitle: 'Button', + }, + argTypes: { + placeholder: { + description: 'The contents of the field when empty', + type: 'string', + table: { + type: { + summary: 'string', + }, + }, + control: 'text', + }, + buttonTitle: { + description: 'The contents of the button', + control: { + type: 'text', + }, + table: { + type: { + summary: 'string', + }, + }, + }, + transactionData: { + control: 'object', + }, + inputValue: { + description: 'The contents of the field', + type: 'number', + table: { + type: { + summary: 'number', + }, + }, + control: 'number', + }, + validity: { + description: 'Validity of the input', + type: { name: 'boolean' }, + table: { + type: { + summary: 'boolean', + }, + }, + control: { + type: 'select', + options: [undefined, true, false], + }, + }, + id: { + description: 'The id of the input', + type: 'string', + table: { + type: { + summary: 'string', + }, + }, + control: 'text', + }, + changeValueHandle: { + control: 'func', + }, + }, +} + +const Template = (args) => + +export const Empty = Template.bind({}) +Empty.args = {} + +export const Btc450 = Template.bind({}) +Btc450.args = { + transactionData: { + fiatName: 'USD', + tokenName: 'BTC', + exchangeRate: 22343.23, + maxValueInToken: 450, + }, + inputValue: 450, +} + +export const BtcSmall = Template.bind({}) +BtcSmall.args = { + transactionData: { + fiatName: 'USD', + tokenName: 'BTC', + exchangeRate: 1, + maxValueInToken: 450, + }, + inputValue: 0.00000001, +} diff --git a/src/components/composed/CryptoField/CryptoField.test.js b/src/components/composed/CryptoField/CryptoField.test.js new file mode 100644 index 00000000..ff540ade --- /dev/null +++ b/src/components/composed/CryptoField/CryptoField.test.js @@ -0,0 +1,181 @@ +import { render, screen, fireEvent } from '@testing-library/react' +// import { getDecimalNumber } from 'src/utils/Helpers/Number/Number' + +import CryptoFiatField from './CryptoFiatField' +import { + SettingsProvider, + AccountProvider, + TransactionProvider, +} from '@Contexts' + +const TRANSACTIONDATASAMPLE = { + fiatName: 'USD', + tokenName: 'BTC', +} + +const PROPSSAMPLE = { + buttonTitle: 'Button title', + placeholder: 'Placeholder', + value: '', + transactionData: TRANSACTIONDATASAMPLE, +} + +const [exchangeRate, maxValueInToken, totalFeeCrypto] = [22343.23, 450, 0.00045] + +test('Render TextField component', () => { + render( + + + + {}} + totalFeeInCrypto={totalFeeCrypto} + /> + , + + + , + , + ) + + const component = screen.getByTestId('crypto-fiat-field') + const input = screen.getByTestId('input') + // const switchButton = screen.getByTestId('crypto-fiat-switch-button') + // const arrowIcons = screen.getAllByTestId('arrow-icon') + const actionButton = screen.getByTestId('button') + const bottomNote = screen.getByTestId('crypto-fiat-bottom-text') + + expect(component).toBeInTheDocument() + + expect(input).toBeInTheDocument() + + fireEvent.change(input, { + target: { value: maxValueInToken }, + }) + + expect(input).toHaveValue(maxValueInToken.toString()) + expect(bottomNote).toHaveTextContent('≈ 10054453.50 USD') + + // expect(switchButton).toBeInTheDocument() + // expect(arrowIcons).toHaveLength(2) + + expect(actionButton).toBeInTheDocument() + expect(actionButton).toHaveTextContent(PROPSSAMPLE.buttonTitle) + + expect(bottomNote).toBeInTheDocument() +}) + +test('Render TextField component fdf', async () => { + render( + + + + {}} + setAmountValidity={() => {}} + totalFeeInCrypto={totalFeeCrypto} + /> + , + + + , + ) + + // const switchButton = screen.getByTestId('crypto-fiat-switch-button') + const actionButton = screen.getByTestId('button') + // const bottomNote = screen.getByTestId('crypto-fiat-bottom-text') + const cryptoInput = screen.getByTestId('input') + + const maxValueInCrypto = maxValueInToken - totalFeeCrypto + + fireEvent.click(actionButton) + expect(cryptoInput).toHaveValue(maxValueInCrypto.toString()) + + // fireEvent.click(switchButton) + // const fiatInput = screen.getByTestId('input') + // expect(fiatInput).toHaveValue( + // getDecimalNumber(maxValueInCrypto * exchangeRate) + // .toString() + // .replace('.', ','), + // ) + + // expect(bottomNote).toHaveTextContent( + // `≈ ${maxValueInCrypto.toString().replace('.', ',')} BTC`, + // ) + + fireEvent.change(cryptoInput, { target: { value: '' } }) +}) + +test('Render TextField when networkType is testnet', () => { + render( + + + + {}} + totalFeeInCrypto={totalFeeCrypto} + /> + , + + + , + , + ) + + const component = screen.getByTestId('crypto-fiat-field') + const input = screen.getByTestId('input') + // const switchButton = screen.getByTestId('crypto-fiat-switch-button') + const actionButton = screen.getByTestId('button') + const bottomNote = screen.getByTestId('crypto-fiat-bottom-text') + + expect(component).toBeInTheDocument() + + expect(input).toBeInTheDocument() + + fireEvent.change(input, { + target: { value: maxValueInToken }, + }) + + expect(input).toHaveValue(maxValueInToken.toString()) + expect(bottomNote).toHaveTextContent('≈ 0.00 USD') + + // expect(switchButton).toBeInTheDocument() + + expect(actionButton).toBeInTheDocument() + expect(actionButton).toHaveTextContent(PROPSSAMPLE.buttonTitle) + + expect(bottomNote).toBeInTheDocument() +}) + +test('Render TextField component without transactionData', () => { + render( + + + + {}} />, + + + , + ) + + expect(screen.queryByTestId('crypto-fiat-field')).not.toBeInTheDocument() +}) diff --git a/src/components/composed/FeeField/FeeField.css b/src/components/composed/FeeField/FeeField.css index b5d2b12a..059ec87c 100644 --- a/src/components/composed/FeeField/FeeField.css +++ b/src/components/composed/FeeField/FeeField.css @@ -16,7 +16,7 @@ } .fee-input-wrapper.ml { - width: 200%; + width: 75%; } .fee-input-wrapper > *:nth-child(1) { diff --git a/src/components/composed/FeeField/FeeFieldML.js b/src/components/composed/FeeField/FeeFieldML.js index 00579a4c..dea263a8 100644 --- a/src/components/composed/FeeField/FeeFieldML.js +++ b/src/components/composed/FeeField/FeeFieldML.js @@ -4,7 +4,6 @@ import React, { import { NetworkContext } from '@Contexts' import { Input } from '@BasicComponents' -import { OptionButtons } from '@ComposedComponents' import './FeeField.css' import { ML as MLHelpers } from '@Helpers' @@ -16,8 +15,6 @@ const FeeFieldML = ({ const { feerate } = useContext(NetworkContext) const timeToFirstConfirmations = '~2 minutes' - const options = [{ name: 'norm', value: feerate }] - const radioButtonValue = 'norm' return (
@@ -29,10 +26,6 @@ const FeeFieldML = ({ /> ML
-

Estimated time for 1st confirmation: {timeToFirstConfirmations}

diff --git a/src/components/composed/index.js b/src/components/composed/index.js index f5f710c1..fa33ff2b 100644 --- a/src/components/composed/index.js +++ b/src/components/composed/index.js @@ -10,6 +10,7 @@ import PopUp from './PopUp/Popup' import ProgressTracker from './ProgressTracker/ProgressTracker' import TextField from './TextField/TextField' import CryptoFiatField from './CryptoFiatField/CryptoFiatField' +import CryptoField from './CryptoField/CryptoField' import FeeField from './FeeField/FeeField' import FeeFieldML from './FeeField/FeeFieldML' import ConnectionErrorPopup from './ConnectionErrorPopup/ConnectionErrorPopup' @@ -34,6 +35,7 @@ export { ProgressTracker, TextField, CryptoFiatField, + CryptoField, FeeField, FeeFieldML, ConnectionErrorPopup, diff --git a/src/components/containers/SendTransaction/AmountField.js b/src/components/containers/SendTransaction/AmountField.js index c930be42..e6a405b0 100644 --- a/src/components/containers/SendTransaction/AmountField.js +++ b/src/components/containers/SendTransaction/AmountField.js @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { CryptoFiatField } from '@ComposedComponents' +import { CryptoField } from '@ComposedComponents' import TransactionField from './TransactionField' import './errorMessages.css' @@ -13,6 +13,7 @@ const AmountField = ({ maxValueInToken, setAmountValidity, totalFeeInCrypto, + amountInCrypto, }) => { const [message, setMessage] = useState(errorMessage) @@ -23,14 +24,15 @@ const AmountField = ({ return ( - setFee(value) const amountChanged = (amount) => { + console.log('amount', amount) calculateTotalFee({ to: addressTo, amount: amount.value, @@ -172,7 +175,7 @@ const SendTransaction = ({ // if (!exchangeRate) return if (amount.currency === transactionData.tokenName) { setOriginalAmount(amount.value) - setAmountInCrypto(amount.value ? Format.BTCValue(amount.value) : '0,00') + setAmountInCrypto(amount.value ? Format.BTCValue(amount.value, decimals) : '') setAmountInFiat( Format.fiatValue( NumbersHelper.floatStringToNumber(amount.value) * @@ -186,6 +189,7 @@ const SendTransaction = ({ setAmountInCrypto( Format.BTCValue( NumbersHelper.floatStringToNumber(amount.value) / exchangeRate, + decimals, ), ) } @@ -321,11 +325,12 @@ const SendTransaction = ({ transactionData={transactionData} amountChanged={amountChanged} exchangeRate={exchangeRate} - maxValueInToken={maxValueInToken} + maxValueInToken={maxValueInToken - totalFeeCrypto} setAmountValidity={setAmountValidity} errorMessage={passErrorMessage} totalFeeInCrypto={totalFeeCrypto} transactionMode={transactionMode} + amountInCrypto={amountInCrypto} /> )} diff --git a/src/components/containers/SendTransaction/SendTransactionConfirmation.js b/src/components/containers/SendTransaction/SendTransactionConfirmation.js index 63317020..56a1110e 100644 --- a/src/components/containers/SendTransaction/SendTransactionConfirmation.js +++ b/src/components/containers/SendTransaction/SendTransactionConfirmation.js @@ -22,8 +22,8 @@ const SendFundConfirmation = ({ }) => { const { networkType } = useContext(SettingsContext) const isTestnet = networkType === AppInfo.NETWORK_TYPES.TESTNET - const amountFiat = isTestnet ? '0,00' : amountInFiat - const feeFiat = isTestnet ? '0,00' : totalFeeFiat + const amountFiat = isTestnet ? '0.00' : amountInFiat + const feeFiat = isTestnet ? '0.00' : totalFeeFiat return (
diff --git a/src/pages/DelegationStake/DelegationStake.js b/src/pages/DelegationStake/DelegationStake.js index 3da31a68..6ff0f1a5 100644 --- a/src/pages/DelegationStake/DelegationStake.js +++ b/src/pages/DelegationStake/DelegationStake.js @@ -62,6 +62,7 @@ const DelegationStakePage = () => { feerate, } = useMlWalletInfo(currentMlAddresses) + console.log('mlBalance', mlBalance) const maxValueToken = mlBalance if (!accountID) { @@ -87,18 +88,22 @@ const DelegationStakePage = () => { approximateFee: 0, }, ) + console.log('transactionSize', transactionSize) const fee = Math.ceil(feerate * (transactionSize / 1000)) + console.log('fee - 1', fee) + const newTransactionSize = await MLTransaction.calculateTransactionSizeInBytes({ utxos: utxos, changeAddress: unusedChangeAddress, - amountToUse: amountToSend, + amountToUse: amountToSend - BigInt(fee), network: networkType, delegationId: address, approximateFee: fee, }) const newFee = Math.ceil(feerate * (newTransactionSize / 1000)) + console.log('fee - 2', newFee) const newFeeInCoins = MLHelpers.getAmountInCoins(Number(newFee)) setTotalFeeFiat(Format.fiatValue(newFeeInCoins * exchangeRate)) setTotalFeeCrypto(newFeeInCoins) diff --git a/src/utils/Helpers/ML/MLTransaction.js b/src/utils/Helpers/ML/MLTransaction.js index 7070c18e..87951934 100644 --- a/src/utils/Helpers/ML/MLTransaction.js +++ b/src/utils/Helpers/ML/MLTransaction.js @@ -247,6 +247,7 @@ const calculateTransactionSizeInBytes = async ({ }) : [] const requireUtxo = [...requireUtxoCoin, ...requireUtxoToken] + console.log('requireUtxo', requireUtxo) const transactionStrings = getUtxoTransactions(requireUtxo) const addressList = getUtxoAddress(requireUtxo) const transactionBytes = getTransactionsBytes(transactionStrings) diff --git a/src/utils/Helpers/Number/Format.js b/src/utils/Helpers/Number/Format.js index 945c215a..7c7d2680 100644 --- a/src/utils/Helpers/Number/Format.js +++ b/src/utils/Helpers/Number/Format.js @@ -1,15 +1,14 @@ import { AppInfo } from '@Constants' -import { NumbersHelper } from '@Helpers' import { getDecimalNumber } from './Number' const getNumber = (value) => - typeof value === 'number' ? value : NumbersHelper.floatStringToNumber(value) + typeof value === 'number' ? value : parseFloat(value) -const BTCValue = (value) => { +const BTCValue = (value, decimals = 8) => { let str = getNumber(value).toString() const decimalIndex = str.indexOf('.') if (decimalIndex !== -1) { - str = str.slice(0, decimalIndex + 9) + str = str.slice(0, decimalIndex + decimals + 1) } return str .replace(/\.0+$/, '')