From f768bbe512d8cf93ec3bb025b1db0e221e8d565e Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Tue, 17 Feb 2026 18:06:09 +0100 Subject: [PATCH] Replace country code by calling code in workflows (#18008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Long overdue PR: replacing deprecated country code in workflows. Will allow to use variables. We keep storing the country code since it allows to display the right flag in picker when there are multiple countries for one calling code. But we do not store country for variables. Capture d’écran 2026-02-17 à 17 25
23 --- ...put.tsx => FormCallingCodeSelectInput.tsx} | 50 ++++++++++++++----- .../components/FormPhoneFieldInput.tsx | 32 +++++------- .../FormCallingCodeSelectInput.stories.tsx | 29 +++++++++++ .../FormCountryCodeSelectInput.stories.tsx | 28 ----------- .../FormPhoneFieldInput.stories.tsx | 25 ++++++---- 5 files changed, 93 insertions(+), 71 deletions(-) rename packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/{FormCountryCodeSelectInput.tsx => FormCallingCodeSelectInput.tsx} (54%) create mode 100644 packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormCallingCodeSelectInput.stories.tsx delete mode 100644 packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormCountryCodeSelectInput.stories.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx similarity index 54% rename from packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx rename to packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx index 902f04456e..2c4b17ac23 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput.tsx @@ -3,22 +3,27 @@ import { useMemo } from 'react'; import { FormSelectFieldInput } from '@/object-record/record-field/ui/form-types/components/FormSelectFieldInput'; import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent'; import { useCountries } from '@/ui/input/components/internal/hooks/useCountries'; +import { isStandaloneVariableString } from '@/workflow/utils/isStandaloneVariableString'; import { t } from '@lingui/core/macro'; -import type { CountryCode } from 'libphonenumber-js'; import { IconCircleOff, type IconComponentProps } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; -export type FormCountryCodeSelectInputUpdatedValue = CountryCode | ''; +export type FormCallingCodeSelectInputUpdatedValue = { + callingCode: string; + countryCode: string; +}; -export const FormCountryCodeSelectInput = ({ +export const FormCallingCodeSelectInput = ({ selectedCountryCode, + selectedCallingCode, onChange, label, readonly = false, VariablePicker, }: { selectedCountryCode: string; - onChange: (countryCode: FormCountryCodeSelectInputUpdatedValue) => void; + selectedCallingCode?: string; + onChange: (value: FormCallingCodeSelectInputUpdatedValue) => void; label?: string; readonly?: boolean; VariablePicker?: VariablePickerComponent; @@ -28,7 +33,7 @@ export const FormCountryCodeSelectInput = ({ const options: SelectOption[] = useMemo(() => { const countryList = countries.map( ({ countryName, countryCode, callingCode, Flag }) => ({ - label: `${countryName} (+${callingCode})`, + label: `+${callingCode} (${countryName})`, value: countryCode, Icon: (props: IconComponentProps) => Flag({ width: props.size, height: props.size }), @@ -36,7 +41,7 @@ export const FormCountryCodeSelectInput = ({ ); return [ { - label: t`No country`, + label: t`No calling code`, value: '', Icon: IconCircleOff, }, @@ -44,24 +49,43 @@ export const FormCountryCodeSelectInput = ({ ]; }, [countries]); - const onCountryCodeChange = (countryCode: string | null) => { + const defaultValue = isStandaloneVariableString(selectedCallingCode) + ? selectedCallingCode + : selectedCountryCode; + + const handleChange = (value: string | null) => { if (readonly) { return; } - if (countryCode === null) { - onChange(''); - } else { - onChange(countryCode as CountryCode); + if (value === null) { + onChange({ callingCode: '', countryCode: '' }); + return; } + + if (isStandaloneVariableString(value)) { + onChange({ callingCode: value, countryCode: '' }); + return; + } + + const matchingCountry = countries.find( + (countryItem) => countryItem.countryCode === value, + ); + + const callingCode = matchingCountry?.callingCode; + + onChange({ + callingCode: callingCode ? `+${callingCode}` : '', + countryCode: value, + }); }; return ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx index 0e8fb96e22..7139216639 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormPhoneFieldInput.tsx @@ -1,15 +1,14 @@ -import { t } from '@lingui/core/macro'; import { - FormCountryCodeSelectInput, - type FormCountryCodeSelectInputUpdatedValue, -} from '@/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput'; + FormCallingCodeSelectInput, + type FormCallingCodeSelectInputUpdatedValue, +} from '@/object-record/record-field/ui/form-types/components/FormCallingCodeSelectInput'; import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputContainer'; import { FormNestedFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormNestedFieldInputContainer'; import { FormNumberFieldInput } from '@/object-record/record-field/ui/form-types/components/FormNumberFieldInput'; import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent'; import { type FieldPhonesValue } from '@/object-record/record-field/ui/types/FieldMetadata'; import { InputLabel } from '@/ui/input/components/InputLabel'; -import { getCountryCallingCode } from 'libphonenumber-js'; +import { t } from '@lingui/core/macro'; type FormPhoneFieldInputProps = { label?: string; @@ -26,19 +25,12 @@ export const FormPhoneFieldInput = ({ readonly, VariablePicker, }: FormPhoneFieldInputProps) => { - const handleCountryChange = ( - newCountry: FormCountryCodeSelectInputUpdatedValue, + const handleCallingCodeChange = ( + newValue: FormCallingCodeSelectInputUpdatedValue, ) => { - let newCallingCode; - if (newCountry === '') { - newCallingCode = ''; - } else { - newCallingCode = getCountryCallingCode(newCountry); - } - onChange({ - primaryPhoneCountryCode: newCountry, - primaryPhoneCallingCode: newCallingCode, + primaryPhoneCountryCode: newValue.countryCode, + primaryPhoneCallingCode: newValue.callingCode, primaryPhoneNumber: defaultValue?.primaryPhoneNumber ?? '', }); }; @@ -55,11 +47,13 @@ export const FormPhoneFieldInput = ({ {label && {label}} - = { + title: 'UI/Data/Field/Form/Input/FormCallingCodeSelectInput', + component: FormCallingCodeSelectInput, + args: { + label: 'Calling Code', + }, + argTypes: {}, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + selectedCountryCode: 'FR', + selectedCallingCode: '+33', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await canvas.findByText('Calling Code'); + }, +}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormCountryCodeSelectInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormCountryCodeSelectInput.stories.tsx deleted file mode 100644 index 1ffb54744b..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormCountryCodeSelectInput.stories.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { type Meta, type StoryObj } from '@storybook/react-vite'; -import { within } from 'storybook/test'; - -import { FormCountryCodeSelectInput } from '@/object-record/record-field/ui/form-types/components/FormCountryCodeSelectInput'; - -const meta: Meta = { - title: 'UI/Data/Field/Form/Input/FormCountryCodeSelectInput', - component: FormCountryCodeSelectInput, - args: { - label: 'Country Code', - }, - argTypes: {}, -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - args: { - selectedCountryCode: 'FR', - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - - await canvas.findByText('Country Code'); - }, -}; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx index 56922c5a85..5cfb1ef3ac 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/__stories__/FormPhoneFieldInput.stories.tsx @@ -22,7 +22,7 @@ type Story = StoryObj; const defaultPhoneValue: FieldPhonesValue = { primaryPhoneNumber: '0612345678', primaryPhoneCountryCode: 'FR', - primaryPhoneCallingCode: '33', + primaryPhoneCallingCode: '+33', }; const FormPhoneFieldInputWithState = ({ @@ -66,7 +66,8 @@ export const WithVariablesAsDefaultValues: Story = { args: { label: 'Phone', defaultValue: { - primaryPhoneCountryCode: `{{${MOCKED_STEP_ID}.name}}`, + primaryPhoneCountryCode: '', + primaryPhoneCallingCode: `{{${MOCKED_STEP_ID}.name}}`, primaryPhoneNumber: `{{${MOCKED_STEP_ID}.amount.amountMicros}}`, }, VariablePicker: () =>
VariablePicker
, @@ -74,12 +75,12 @@ export const WithVariablesAsDefaultValues: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const countryCodeVariable = await canvas.findByText('Name'); - expect(countryCodeVariable).toBeVisible(); + const callingCodeVariable = await canvas.findByText('Name'); + expect(callingCodeVariable).toBeVisible(); const variablePickers = await canvas.findAllByText('VariablePicker'); - expect(variablePickers).toHaveLength(1); + expect(variablePickers).toHaveLength(2); for (const variablePicker of variablePickers) { expect(variablePicker).toBeVisible(); @@ -115,14 +116,16 @@ export const SelectingVariables: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); - const countryCodeDefaultValue = await canvas.findByText('No country'); - expect(countryCodeDefaultValue).toBeVisible(); + const callingCodeDefaultValue = await canvas.findByText('No calling code'); + + expect(callingCodeDefaultValue).toBeVisible(); const phoneNumberDefaultValue = await canvas.findByPlaceholderText('Enter phone number'); expect(phoneNumberDefaultValue).toHaveDisplayValue(''); - const phoneNumberVariablePicker = await canvas.findByText('Add variable'); + const addVariableButtons = await canvas.findAllByText('Add variable'); + const phoneNumberVariablePicker = addVariableButtons[1]; await userEvent.click(phoneNumberVariablePicker); @@ -148,10 +151,10 @@ export const Disabled: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const countryInput = await canvas.findByText('No country'); - expect(countryInput).toBeVisible(); + const callingCodeInput = await canvas.findByText('No calling code'); + expect(callingCodeInput).toBeVisible(); - await userEvent.click(countryInput); + await userEvent.click(callingCodeInput); const searchInputInModal = canvas.queryByPlaceholderText('Search'); expect(searchInputInModal).not.toBeInTheDocument();