Default address country 🗺️ & Phone prefix ☎️ (#8614)

# 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 <charles@twenty.com>
This commit is contained in:
Guillim
2024-12-02 13:34:05 +01:00
committed by GitHub
parent 39a9cd0d51
commit 0527bc296e
28 changed files with 617 additions and 108 deletions

View File

@ -4,28 +4,37 @@ import { ContactLink } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
type PhoneDisplayProps = {
value: string | null | undefined;
interface PhoneDisplayProps {
value: PhoneDisplayValueProps;
}
type PhoneDisplayValueProps = {
number: string | null | undefined;
callingCode: string | null | undefined;
};
// TODO: see if we can find a faster way to format the phone number
export const PhoneDisplay = ({ value }: PhoneDisplayProps) => {
if (!isDefined(value)) {
return <ContactLink href="#">{value}</ContactLink>;
}
export const PhoneDisplay = ({
value: { number, callingCode },
}: PhoneDisplayProps) => {
if (!isDefined(number)) return <ContactLink href="#">{number}</ContactLink>;
const callingCodeSanitized = callingCode?.replace('+', '');
let parsedPhoneNumber: PhoneNumber | null = null;
try {
// TODO: parse according to locale not hard coded FR
parsedPhoneNumber = parsePhoneNumber(value, 'FR');
parsedPhoneNumber = parsePhoneNumber(number, {
defaultCallingCode: callingCodeSanitized || '1',
});
} catch (error) {
return <ContactLink href="#">{value}</ContactLink>;
if (!(error instanceof Error))
return <ContactLink href="#">{number}</ContactLink>;
if (error.message === 'NOT_A_NUMBER')
return <ContactLink href="#">{`+${callingCodeSanitized}`}</ContactLink>;
return <ContactLink href="#">{number}</ContactLink>;
}
const URI = parsedPhoneNumber.getURI();
const formatedPhoneNumber = parsedPhoneNumber.formatInternational();
return (
<ContactLink
href={URI}
@ -33,7 +42,7 @@ export const PhoneDisplay = ({ value }: PhoneDisplayProps) => {
event.stopPropagation();
}}
>
{formatedPhoneNumber || value}
{formatedPhoneNumber || number}
</ContactLink>
);
};

View File

@ -36,16 +36,16 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
value?.primaryPhoneNumber
? {
number: value.primaryPhoneNumber,
countryCode: value.primaryPhoneCountryCode,
callingCode: value.primaryPhoneCountryCode,
}
: null,
...parseAdditionalPhones(value?.additionalPhones),
]
.filter(isDefined)
.map(({ number, countryCode }) => {
.map(({ number, callingCode }) => {
return {
number,
countryCode,
callingCode,
};
}),
[
@ -65,9 +65,9 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
return isFocused ? (
<ExpandableList isChipCountDisplayed>
{phones.map(({ number, countryCode }, index) => {
{phones.map(({ number, callingCode }, index) => {
const { parsedPhone, invalidPhone } =
parsePhoneNumberOrReturnInvalidValue(countryCode + number);
parsePhoneNumberOrReturnInvalidValue(`+${callingCode}` + number);
const URI = parsedPhone?.getURI();
return (
<RoundedLink
@ -82,9 +82,9 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
</ExpandableList>
) : (
<StyledContainer>
{phones.map(({ number, countryCode }, index) => {
{phones.map(({ number, callingCode }, index) => {
const { parsedPhone, invalidPhone } =
parsePhoneNumberOrReturnInvalidValue(countryCode + number);
parsePhoneNumberOrReturnInvalidValue(`+${callingCode}` + number);
const URI = parsedPhone?.getURI();
return (
<RoundedLink

View File

@ -26,7 +26,9 @@ type CallToActionButton = {
Icon?: IconComponent;
};
export type SelectProps<Value extends string | number | boolean | null> = {
export type SelectValue = string | number | boolean | null;
export type SelectProps<Value extends SelectValue> = {
className?: string;
disabled?: boolean;
selectSizeVariant?: SelectSizeVariant;
@ -57,7 +59,7 @@ const StyledLabel = styled.span`
margin-bottom: ${({ theme }) => theme.spacing(1)};
`;
export const Select = <Value extends string | number | boolean | null>({
export const Select = <Value extends SelectValue>({
className,
disabled: disabledFromProps,
selectSizeVariant,

View File

@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { IconComponentProps } from 'twenty-ui';
import { IconCircleOff, IconComponentProps } from 'twenty-ui';
import { SELECT_COUNTRY_DROPDOWN_ID } from '@/ui/input/components/internal/country/constants/SelectCountryDropdownId';
import { useCountries } from '@/ui/input/components/internal/hooks/useCountries';
@ -15,12 +15,20 @@ export const CountrySelect = ({
const countries = useCountries();
const options: SelectOption<string>[] = useMemo(() => {
return countries.map<SelectOption<string>>(({ countryName, Flag }) => ({
label: countryName,
value: countryName,
Icon: (props: IconComponentProps) =>
Flag({ width: props.size, height: props.size }), // TODO : improve this ?
}));
const countryList = countries.map<SelectOption<string>>(
({ countryName, Flag }) => ({
label: countryName,
value: countryName,
Icon: (props: IconComponentProps) =>
Flag({ width: props.size, height: props.size }), // TODO : improve this ?
}),
);
countryList.unshift({
label: 'No country',
value: '',
Icon: IconCircleOff,
});
return countryList;
}, [countries]);
return (

View File

@ -1,8 +1,8 @@
import * as Flags from 'country-flag-icons/react/3x2';
import { CountryCallingCode } from 'libphonenumber-js';
import { CountryCallingCode, CountryCode } from 'libphonenumber-js';
export type Country = {
countryCode: string;
countryCode: CountryCode;
countryName: string;
callingCode: CountryCallingCode;
Flag: Flags.FlagComponent;