use micros converters in FormCurrencyFieldInput (#16330)

Fixes: #15887

The currency fields were expecting values in micros (multiplied by
10^6), but
this requirement was not indicated anywhere in the workflow UI. Users
had to
manually add a code node to multiply amounts by 10^6 before using the
new
currency value.

This change integrates the micros conversion logic directly into
FormCurrencyFieldInput by using convertCurrencyAmountToCurrencyMicros
and
convertCurrencyMicrosToCurrencyAmount utilities. This allows users to
input
human-readable decimal amounts (e.g., 3.21 for $3.21) in the form, which
are
automatically converted to micros internally for storage and processing.

This eliminates the need for users to add separate code nodes for
conversion
and improves the user experience by making the expected input format
clear.
This commit is contained in:
Lucky Goyal
2025-12-08 22:20:50 +05:30
committed by GitHub
parent 9efeb180d6
commit d7dbb87a8f
2 changed files with 34 additions and 6 deletions
@@ -9,6 +9,34 @@ import { InputLabel } from '@/ui/input/components/InputLabel';
import { useMemo } from 'react';
import { type CurrencyCode } from 'twenty-shared/constants';
import { IconCircleOff } from 'twenty-ui/display';
import {
convertCurrencyAmountToCurrencyMicros,
convertCurrencyMicrosToCurrencyAmount,
} from '~/utils/convertCurrencyToCurrencyMicros';
const formatMicrosToDisplayAmount = (
amountMicros: string | number | null | undefined,
): string | number => {
if (amountMicros == null) {
return '';
}
if (Number.isFinite(+amountMicros)) {
return convertCurrencyMicrosToCurrencyAmount(+amountMicros);
}
return amountMicros;
};
const parseDisplayAmountToMicros = (
displayAmount: string | number | null,
): number | null => {
if (displayAmount == null) {
return null;
}
if (Number.isFinite(+displayAmount)) {
return convertCurrencyAmountToCurrencyMicros(Number(displayAmount));
}
return null;
};
type FormCurrencyFieldInputProps = {
label?: string;
@@ -41,7 +69,7 @@ export const FormCurrencyFieldInput = ({
) => {
onChange({
currencyCode: defaultValue?.currencyCode ?? null,
amountMicros: newAmountMicros ?? null,
amountMicros: parseDisplayAmountToMicros(newAmountMicros),
});
};
@@ -65,11 +93,11 @@ export const FormCurrencyFieldInput = ({
readonly={readonly}
/>
<FormNumberFieldInput
label="Amount Micros"
defaultValue={defaultValue?.amountMicros ?? ''}
label="Amount"
defaultValue={formatMicrosToDisplayAmount(defaultValue?.amountMicros)}
onChange={handleAmountMicrosChange}
VariablePicker={VariablePicker}
placeholder="Set 3210000 for $3.21"
placeholder="Set 3.21 for $3.21"
readonly={readonly}
/>
</FormNestedFieldInputContainer>
@@ -32,7 +32,7 @@ export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Currency Code');
await canvas.findByText('Amount Micros');
await canvas.findByText('Amount');
},
};
@@ -81,7 +81,7 @@ export const Disabled: Story = {
const currency = await canvas.findByText(/USD/);
expect(currency).toBeVisible();
const amountInput = await canvas.findByDisplayValue('44000000');
const amountInput = await canvas.findByDisplayValue('44');
expect(amountInput).toBeVisible();
expect(amountInput).toBeDisabled();