diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.stories.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.stories.tsx
new file mode 100644
index 0000000000..309997860c
--- /dev/null
+++ b/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.stories.tsx
@@ -0,0 +1,36 @@
+import {ReactNode} from 'react';
+import {useArgs} from '@storybook/preview-api';
+import type {Meta, StoryObj} from '@storybook/react';
+
+import CurrencyField from './CurrencyField';
+
+const meta = {
+ title: 'Global / Form / Currency field',
+ component: CurrencyField,
+ tags: ['autodocs'],
+ decorators: [(_story: () => ReactNode) => (
{_story()}
)],
+ argTypes: {
+ hint: {
+ control: 'text'
+ },
+ rightPlaceholder: {
+ control: 'text'
+ }
+ }
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const WithValue: Story = {
+ render: function Component(args) {
+ const [, updateArgs] = useArgs();
+
+ return updateArgs({valueInCents})} />;
+ },
+ args: {
+ title: 'Amount',
+ hint: 'Notice how the value is the integer number of cents',
+ valueInCents: 500
+ }
+};
diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.tsx
index 16a30256c1..939c85d966 100644
--- a/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.tsx
+++ b/apps/admin-x-settings/src/admin-x-ds/global/form/CurrencyField.tsx
@@ -2,17 +2,25 @@ import React, {useState} from 'react';
import TextField, {TextFieldProps} from './TextField';
import {currencyFromDecimal, currencyToDecimal} from '../../../utils/currency';
-export type CurrencyFieldProps = Omit & {
- currency?: string
- onChange?: (value: number) => void
+export type CurrencyFieldProps = Omit & {
+ valueInCents?: number;
+ currency?: string;
+ onChange?: (cents: number) => void;
}
+/**
+ * A CurrencyField is a special type of [TextField](?path=/docs/global-form-textfield--docs) with
+ * some parsing to input currency values. While editing you can enter any number of decimals, but
+ * the value in `onChange` will be rounded and multiplied to get an integer number of cents.
+ *
+ * Available options are generally the same as TextField.
+ */
const CurrencyField: React.FC = ({
- value,
+ valueInCents,
onChange,
...props
}) => {
- const [localValue, setLocalValue] = useState(currencyToDecimal(parseInt(value || '0')).toString());
+ const [localValue, setLocalValue] = useState(currencyToDecimal(valueInCents || 0).toString());
// While the user is editing we allow more lenient input, e.g. "1.32.566" to make it easier to type and change
const stripNonNumeric = (input: string) => input.replace(/[^\d.]+/g, '');
diff --git a/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx b/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx
index e6458d7f4d..1885168457 100644
--- a/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx
+++ b/apps/admin-x-settings/src/components/settings/membership/TipsOrDonations.tsx
@@ -105,7 +105,7 @@ const TipsOrDonations: React.FC<{ keywords: string[] }> = ({keywords}) => {
/>
)}
title='Suggested amount'
- value={donationsSuggestedAmount}
+ valueInCents={parseInt(donationsSuggestedAmount)}
onBlur={validate}
onChange={cents => updateSetting('donations_suggested_amount', cents.toString())}
onKeyDown={() => clearError('donationsSuggestedAmount')}
diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx
index b4853615e3..773cd28358 100644
--- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx
+++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx
@@ -170,7 +170,7 @@ const TierDetailModal: React.FC = ({tier}) => {
placeholder='1'
rightPlaceholder={`${formState.currency}/month`}
title='Monthly price'
- value={formState.monthly_price?.toString() || ''}
+ valueInCents={formState.monthly_price || 0}
hideTitle
onBlur={() => validators.monthly_price()}
onChange={price => updateForm(state => ({...state, monthly_price: price}))}
@@ -181,7 +181,7 @@ const TierDetailModal: React.FC = ({tier}) => {
placeholder='10'
rightPlaceholder={`${formState.currency}/year`}
title='Yearly price'
- value={formState.yearly_price?.toString() || ''}
+ valueInCents={formState.yearly_price || 0}
hideTitle
onBlur={() => validators.yearly_price()}
onChange={price => updateForm(state => ({...state, yearly_price: price}))}