Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { CurrencyCode } from 'twenty-shared/constants';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/formatNumber';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';

export const getFieldMetadataItemInitialValues = (
fieldMetadataItem: FieldMetadataItem | null | undefined,
) => {
if (fieldMetadataItem?.type !== FieldMetadataType.CURRENCY) {
return {
settings: fieldMetadataItem?.settings ?? undefined,
defaultValue: fieldMetadataItem?.defaultValue ?? undefined,
};
}

const settings = fieldMetadataItem.settings ?? {
format: 'short',
decimals: DEFAULT_DECIMAL_VALUE,
};

const defaultValue = fieldMetadataItem.defaultValue
? {
amountMicros: fieldMetadataItem.defaultValue.amountMicros ?? null,
currencyCode: fieldMetadataItem.defaultValue.currencyCode,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing validation for currencyCode when defaultValue exists. The existing useCurrencySettingsFormInitialValues.ts hook validates that currencyCode is a non-empty string (after stripping quotes) and falls back to USD if invalid. This function should apply the same validation to handle cases where defaultValue exists but currencyCode is missing or empty.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/src/modules/object-metadata/utils/getFieldMetadataItemInitialValues.ts, line 25:

<comment>Missing validation for `currencyCode` when `defaultValue` exists. The existing `useCurrencySettingsFormInitialValues.ts` hook validates that `currencyCode` is a non-empty string (after stripping quotes) and falls back to USD if invalid. This function should apply the same validation to handle cases where `defaultValue` exists but `currencyCode` is missing or empty.</comment>

<file context>
@@ -0,0 +1,33 @@
+  const defaultValue = fieldMetadataItem.defaultValue
+    ? {
+        amountMicros: fieldMetadataItem.defaultValue.amountMicros ?? null,
+        currencyCode: fieldMetadataItem.defaultValue.currencyCode,
+      }
+    : {
</file context>

✅ Addressed in be42bfc

}
: {
amountMicros: null,
currencyCode: applySimpleQuotesToString(CurrencyCode.USD),
};

return { settings, defaultValue };
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMe
import { useUpdateOneFieldMetadataItem } from '@/object-metadata/hooks/useUpdateOneFieldMetadataItem';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { formatFieldMetadataItemInput } from '@/object-metadata/utils/formatFieldMetadataItemInput';
import { getFieldMetadataItemInitialValues } from '@/object-metadata/utils/getFieldMetadataItemInitialValues';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
Expand All @@ -33,10 +34,10 @@ import { useRecoilState } from 'recoil';
import { AppPath, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import {
H2Title,
IconArchive,
IconArchiveOff,
IconTrash,
H2Title,
IconArchive,
IconArchiveOff,
IconTrash,
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
Expand Down Expand Up @@ -107,6 +108,10 @@ export const SettingsObjectFieldEdit = () => {
const getRelationMetadata = useGetRelationMetadata();
const { updateOneFieldMetadataItem } = useUpdateOneFieldMetadataItem();

const { settings, defaultValue } = getFieldMetadataItemInitialValues(
fieldMetadataItem,
);

const formConfig = useForm<SettingsDataModelFieldEditFormValues>({
mode: 'onTouched',
resolver: zodResolver(settingsFieldFormSchema()),
Expand All @@ -116,7 +121,8 @@ export const SettingsObjectFieldEdit = () => {
label: fieldMetadataItem?.label ?? '',
description: fieldMetadataItem?.description,
isLabelSyncedWithName: fieldMetadataItem?.isLabelSyncedWithName ?? true,
settings: fieldMetadataItem?.settings,
settings,
defaultValue,
},
});
Comment on lines 116 to 126

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name field is intentionally excluded and it has been the case before the changes. It's managed by a child component (SettingsDataModelFieldIconLabelForm) which has its own Controller with defaultValue={fieldMetadataItem?.name}.

Comment on lines 117 to 126

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the values prop to avoid useEffect, consistent with the codebase patterns. React Hook Form performs deep comparison, so isDirty is not reset when values are unchanged.

Comment on lines 120 to 126

This comment was marked as outdated.


Comment on lines 120 to 127

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values prop is appropriate here because:

  • It syncs the form with external state (fieldMetadataItem)
  • React Hook Form performs deep comparison, so isDirty should work correctly
  • It avoids useEffect, following the codebase pattern

Expand Down
Loading