Skip to content

Commit

Permalink
Default address country 🗺️ & Phone prefix ☎️ (#8614)
Browse files Browse the repository at this point in the history
# Default address 🗺️ country & Phone ☎️ country

We add the ability to add a Default address country and a default Phone
country for fields in the Data model.

fix #8081

---------

Co-authored-by: Charles Bochet <[email protected]>
  • Loading branch information
guillim and charlesBochet authored Dec 2, 2024
1 parent 39a9cd0 commit 0527bc2
Show file tree
Hide file tree
Showing 28 changed files with 617 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';

export const FIELD_NOT_OVERWRITTEN_AT_DRAFT = [
FieldMetadataType.Address,
FieldMetadataType.Phones,
FieldMetadataType.Links,
];
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export const useNumberField = () => {

const persistNumberField = (newValue: string) => {
if (fieldDefinition?.metadata?.settings?.type === 'percentage') {
newValue = newValue.replaceAll('%', '');
if (!canBeCastAsNumberOrNull(newValue)) {
const newValueEscaped = newValue.replaceAll('%', '');
if (!canBeCastAsNumberOrNull(newValueEscaped)) {
return;
}
const castedValue = castAsNumberOrNull(newValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const MultiItemFieldInput = <T,>({
break;
case FieldMetadataType.Phones:
item = items[index] as PhoneRecord;
setInputValue(item.countryCode + item.number);
setInputValue(`+${item.callingCode}` + item.number);
break;
case FieldMetadataType.Emails:
item = items[index] as string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { E164Number, parsePhoneNumber } from 'libphonenumber-js';
import { useMemo } from 'react';
import ReactPhoneNumberInput from 'react-phone-number-input';
import 'react-phone-number-input/style.css';
import { isDefined, TEXT_INPUT_STYLE } from 'twenty-ui';
import { TEXT_INPUT_STYLE, isDefined } from 'twenty-ui';

import { MultiItemFieldInput } from './MultiItemFieldInput';

import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
import { PhoneCountryPickerDropdownButton } from '@/ui/input/components/internal/phone/components/PhoneCountryPickerDropdownButton';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';

const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)`
font-family: ${({ theme }) => theme.font.family};
Expand Down Expand Up @@ -48,33 +50,41 @@ type PhonesFieldInputProps = {
};

export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
const { persistPhonesField, hotkeyScope, fieldValue } = usePhonesField();
const { persistPhonesField, hotkeyScope, draftValue, fieldDefinition } =
usePhonesField();

const phones = useMemo<{ number: string; countryCode: string }[]>(
() =>
[
fieldValue.primaryPhoneNumber
? {
number: fieldValue.primaryPhoneNumber,
countryCode: fieldValue.primaryPhoneCountryCode,
}
: null,
...(fieldValue.additionalPhones ?? []),
].filter(isDefined),
[
fieldValue.primaryPhoneNumber,
fieldValue.primaryPhoneCountryCode,
fieldValue.additionalPhones,
],
);
const phones = useMemo<{ number: string; callingCode: string }[]>(() => {
if (!isDefined(draftValue)) {
return [];
}
return [
draftValue.primaryPhoneNumber
? {
number: draftValue.primaryPhoneNumber,
callingCode: draftValue.primaryPhoneCountryCode,
}
: null,
...(draftValue.additionalPhones ?? []),
].filter(isDefined);
}, [draftValue]);

const defaultCallingCode =
stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
) ?? '+1';

// TODO : improve once we store the real country code
const defaultCountry = useCountries().find(
(obj) => obj.callingCode === defaultCallingCode,
)?.countryCode;

const handlePersistPhones = (
updatedPhones: { number: string; countryCode: string }[],
updatedPhones: { number: string; callingCode: string }[],
) => {
const [nextPrimaryPhone, ...nextAdditionalPhones] = updatedPhones;
persistPhonesField({
primaryPhoneNumber: nextPrimaryPhone?.number ?? '',
primaryPhoneCountryCode: nextPrimaryPhone?.countryCode ?? '',
primaryPhoneCountryCode: nextPrimaryPhone?.callingCode ?? '',
additionalPhones: nextAdditionalPhones,
});
};
Expand All @@ -93,12 +103,12 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
if (phone !== undefined) {
return {
number: phone.nationalNumber,
countryCode: `+${phone.countryCallingCode}`,
callingCode: `${phone.countryCallingCode}`,
};
}
return {
number: '',
countryCode: '',
callingCode: '',
};
}}
renderItem={({
Expand Down Expand Up @@ -128,6 +138,7 @@ export const PhonesFieldInput = ({ onCancel }: PhonesFieldInputProps) => {
international={true}
withCountryCallingCode={true}
countrySelectComponent={PhoneCountryPickerDropdownButton}
defaultCountry={defaultCountry}
/>
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type PhonesFieldMenuItemProps = {
onEdit?: () => void;
onSetAsPrimary?: () => void;
onDelete?: () => void;
phone: { number: string; countryCode: string };
phone: { number: string; callingCode: string };
};

export const PhonesFieldMenuItem = ({
Expand All @@ -22,7 +22,7 @@ export const PhonesFieldMenuItem = ({
<MultiItemFieldMenuItem
dropdownId={dropdownId}
isPrimary={isPrimary}
value={phone.countryCode + phone.number}
value={{ number: phone.number, callingCode: phone.callingCode }}
onEdit={onEdit}
onSetAsPrimary={onSetAsPrimary}
onDelete={onDelete}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import { CurrencyCode } from './CurrencyCode';
export type FieldUuidMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldBooleanMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldTextMetadata = {
Expand Down Expand Up @@ -61,80 +61,80 @@ export type FieldLinkMetadata = {
objectMetadataNameSingular?: string;
placeHolder: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldLinksMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldCurrencyMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
placeHolder: string;
isPositive?: boolean;
settings?: Record<string, never>;
settings?: null;
};

export type FieldFullNameMetadata = {
objectMetadataNameSingular?: string;
placeHolder: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldEmailMetadata = {
objectMetadataNameSingular?: string;
placeHolder: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldEmailsMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldPhoneMetadata = {
objectMetadataNameSingular?: string;
placeHolder: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldRatingMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldAddressMetadata = {
objectMetadataNameSingular?: string;
placeHolder: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldRawJsonMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
placeHolder: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldRichTextMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldPositionMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldRelationMetadata = {
Expand All @@ -146,47 +146,47 @@ export type FieldRelationMetadata = {
relationType?: RelationDefinitionType;
targetFieldMetadataName?: string;
useEditButton?: boolean;
settings?: Record<string, never>;
settings?: null;
};

export type FieldSelectMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
options: { label: string; color: ThemeColor; value: string }[];
isNullable: boolean;
settings?: Record<string, never>;
settings?: null;
};

export type FieldMultiSelectMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
options: { label: string; color: ThemeColor; value: string }[];
settings?: Record<string, never>;
settings?: null;
};

export type FieldActorMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldArrayMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
values: { label: string; value: string }[];
settings?: Record<string, never>;
settings?: null;
};

export type FieldPhonesMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldTsVectorMetadata = {
objectMetadataNameSingular?: string;
fieldName: string;
settings?: Record<string, never>;
settings?: null;
};

export type FieldMetadata =
Expand Down Expand Up @@ -265,7 +265,7 @@ export type FieldActorValue = {

export type FieldArrayValue = string[];

export type PhoneRecord = { number: string; countryCode: string };
export type PhoneRecord = { number: string; callingCode: string };

export type FieldPhonesValue = {
primaryPhoneNumber: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { FieldAddressValue } from '../FieldMetadata';

const addressSchema = z.object({
export const addressSchema = z.object({
addressStreet1: z.string(),
addressStreet2: z.string().nullable(),
addressCity: z.string().nullable(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const phonesSchema = z.object({
primaryPhoneNumber: z.string(),
primaryPhoneCountryCode: z.string(),
additionalPhones: z
.array(z.object({ number: z.string(), countryCode: z.string() }))
.array(z.object({ number: z.string(), callingCode: z.string() }))
.nullable(),
}) satisfies z.ZodType<FieldPhonesValue>;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency';
import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue';
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
import { isFieldNumberValue } from '@/object-record/record-field/types/guards/isFieldNumberValue';
import { isFieldPhones } from '@/object-record/record-field/types/guards/isFieldPhones';
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
import { computeEmptyDraftValue } from '@/object-record/record-field/utils/computeEmptyDraftValue';
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { stripSimpleQuotesFromString } from '~/utils/string/stripSimpleQuotesFromString';

type computeDraftValueFromFieldValueParams<FieldValue> = {
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type' | 'metadata'>;
Expand Down Expand Up @@ -42,6 +45,38 @@ export const computeDraftValueFromFieldValue = <FieldValue>({
} as unknown as FieldInputDraftValue<FieldValue>;
}

if (isFieldAddress(fieldDefinition)) {
if (
isFieldValueEmpty({ fieldValue, fieldDefinition }) &&
!!fieldDefinition?.defaultValue?.addressCountry
) {
return {
...fieldValue,
addressCountry: stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.addressCountry,
),
} as unknown as FieldInputDraftValue<FieldValue>;
}

return fieldValue as FieldInputDraftValue<FieldValue>;
}

if (isFieldPhones(fieldDefinition)) {
if (
isFieldValueEmpty({ fieldValue, fieldDefinition }) &&
!!fieldDefinition?.defaultValue?.primaryPhoneCountryCode
) {
return {
...fieldValue,
primaryPhoneCountryCode: stripSimpleQuotesFromString(
fieldDefinition?.defaultValue?.primaryPhoneCountryCode,
),
} as unknown as FieldInputDraftValue<FieldValue>;
}

return fieldValue as FieldInputDraftValue<FieldValue>;
}

if (
isFieldNumber(fieldDefinition) &&
isFieldNumberValue(fieldValue) &&
Expand Down
Loading

0 comments on commit 0527bc2

Please sign in to comment.