From 072a41950d7efab144bab079be3641df8095c985 Mon Sep 17 00:00:00 2001 From: apX13_ Date: Sat, 17 Aug 2024 17:34:04 +0530 Subject: [PATCH] fix: amount input field to properly interpret BTC (#800) --- src/components/BitcoinAmountInput.test.tsx | 21 ++++ src/components/BitcoinAmountInput.tsx | 139 +++++++++++---------- 2 files changed, 95 insertions(+), 65 deletions(-) diff --git a/src/components/BitcoinAmountInput.test.tsx b/src/components/BitcoinAmountInput.test.tsx index aab58e3a..b46b6b06 100644 --- a/src/components/BitcoinAmountInput.test.tsx +++ b/src/components/BitcoinAmountInput.test.tsx @@ -95,4 +95,25 @@ describe('', () => { expect(inputElement.dataset.displayUnit).toBe(undefined) expect(inputElement.dataset.displayValue).toBe('') }) + + it('amount 1.0 should be interpreted as 1 BTC', async () => { + setup({ + label: 'test-label', + }) + const inputElement = screen.getByLabelText('test-label') + + await user.type(inputElement, '1.0') + + expect(inputElement).toHaveFocus() + expect(inputElement.dataset.value).toBe('100000000') + expect(inputElement.dataset.displayUnit).toBe('BTC') + expect(inputElement.dataset.displayValue).toBe(`1.0`) + + await user.tab() + + expect(inputElement).not.toHaveFocus() + expect(inputElement.dataset.value).toBe('100000000') + expect(inputElement.dataset.displayUnit).toBe('BTC') + expect(inputElement.dataset.displayValue).toBe(`1.00 000 000`) + }) }) diff --git a/src/components/BitcoinAmountInput.tsx b/src/components/BitcoinAmountInput.tsx index bca70df8..d4f565b0 100644 --- a/src/components/BitcoinAmountInput.tsx +++ b/src/components/BitcoinAmountInput.tsx @@ -61,6 +61,78 @@ const BitcoinAmountInput = forwardRef( : undefined }, [field, inputType]) + const handleBlur = (e: React.FocusEvent) => { + setInputType({ + type: 'text', + inputMode: 'decimal', + }) + + let displayValue = String(field.value?.value || '') + if (isValidNumber(field.value?.value)) { + displayValue = formatBtcDisplayValue(field.value!.value!) + } + + form.setFieldValue( + field.name, + { + ...field.value, + displayValue, + }, + false, + ) + field.onBlur(e) + } + + const handleChange = (e: React.ChangeEvent) => { + const rawUserInputOrEmpty = e.target.value ?? '' + const validNumberRegex = /^-?\d*\.?\d*$/ + if (!validNumberRegex.test(rawUserInputOrEmpty)) { + return + } + const floatValueOrNan = parseFloat(rawUserInputOrEmpty) + if (!isValidNumber(floatValueOrNan)) { + form.setFieldValue( + field.name, + { + ...field.value, + value: null, + userRawInputValue: e.target.value, + displayValue: e.target.value, + }, + true, + ) + return + } else { + const value: number = floatValueOrNan + let numberValues: string | undefined + const unit = + rawUserInputOrEmpty.includes('.') && parseFloat(rawUserInputOrEmpty) + ? unitFromValue(String(rawUserInputOrEmpty)) + : unitFromValue(String(value)) + if (unit === 'BTC') { + const splitted = String(value).split('.') + const [integerPart, fractionalPart = ''] = splitted + const paddedFractionalPart = fractionalPart.padEnd(8, '0').substring(0, 8) + numberValues = `${integerPart}${paddedFractionalPart}` + } else { + numberValues = value.toLocaleString('en-US', { + maximumFractionDigits: 0, + useGrouping: false, + }) + } + + form.setFieldValue( + field.name, + { + value: parseInt(numberValues, 10), + userRawInputValue: e.target.value, + displayValue: e.target.value, + }, + true, + ) + } + } + return ( <> @@ -77,7 +149,6 @@ const BitcoinAmountInput = forwardRef( data-display-value={field.value?.displayValue} name={field.name} autoComplete="off" - type={inputType.type} inputMode={inputType.inputMode} className={classNames('slashed-zeroes', className)} value={ @@ -92,70 +163,8 @@ const BitcoinAmountInput = forwardRef( onFocus={() => { setInputType({ type: 'number' }) }} - onBlur={(e) => { - setInputType({ - type: 'text', - inputMode: 'decimal', - }) - - let displayValue = String(field.value?.value || '') - if (isValidNumber(field.value?.value)) { - displayValue = formatBtcDisplayValue(field.value!.value!) - } - - form.setFieldValue( - field.name, - { - ...field.value, - displayValue, - }, - false, - ) - field.onBlur(e) - }} - onChange={(e) => { - const valueOrNan = parseFloat(e.target.value ?? '') - - if (!isValidNumber(valueOrNan)) { - form.setFieldValue( - field.name, - { - ...field.value, - value: null, - userRawInputValue: e.target.value, - displayValue: e.target.value, - }, - true, - ) - return - } else { - const value: number = valueOrNan - - let numberValues: string | undefined - const unit = unitFromValue(String(value)) - if (unit === 'BTC') { - const splitted = String(value).split('.') - const [integerPart, fractionalPart = ''] = splitted - const paddedFractionalPart = fractionalPart.padEnd(8, '0').substring(0, 8) - numberValues = `${integerPart}${paddedFractionalPart}` - } else { - numberValues = value.toLocaleString('en-US', { - maximumFractionDigits: 0, - useGrouping: false, - }) - } - - form.setFieldValue( - field.name, - { - value: parseInt(numberValues, 10), - userRawInputValue: e.target.value, - displayValue: e.target.value, - }, - true, - ) - } - }} + onBlur={handleBlur} + onChange={handleChange} /> {children}