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+$/, '')