fix: add phone mask overrides for Argentina and Finland to prevent digit truncation (#28203)

* fix: add phone mask overrides for Argentina and Finland to prevent digit truncation

Co-Authored-By: susan <susan@cal.com>

* refactor: extract CUSTOM_PHONE_MASKS to shared module to avoid duplication

Address review feedback from cubic-dev-ai: extract CUSTOM_PHONE_MASKS
into a shared phone-masks.ts module so both PhoneInput.tsx and the test
file import from the same source of truth instead of duplicating the
masks. Also fix non-null assertion lint warning in the test file.

Original PR by devin-ai-integration[bot].

Co-Authored-By: bot_apk <apk@cognition.ai>

* fix: move custom message to expect() for toBeGreaterThanOrEqual type compatibility

toBeGreaterThanOrEqual only accepts 1 argument. Move the custom error
message to the expect() call instead.

Co-Authored-By: bot_apk <apk@cognition.ai>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: susan <susan@cal.com>
Co-authored-by: bot_apk <apk@cognition.ai>
Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot]
2026-03-03 13:05:24 +00:00
committed by GitHub
co-authored by bot_apk Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> susan Eunjae Lee Udit Takkar
parent 1b21ead011
commit 2bf45677e8
3 changed files with 76 additions and 8 deletions
@@ -0,0 +1,53 @@
import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-js/max";
import { describe, expect, it } from "vitest";
import { CUSTOM_PHONE_MASKS } from "./phone-masks";
/** Count the digit placeholders (dots) in a react-phone-input-2 mask */
const countMaskDigits = (mask: string): number => (mask.match(/\./g) || []).length;
/**
* Test phone numbers that must be accepted for each country with a custom mask.
* These are realistic mobile and landline numbers validated by libphonenumber-js.
*/
const COUNTRY_TEST_NUMBERS: Record<string, { number: string; description: string }[]> = {
ci: [{ number: "+2250797764877", description: "Ivory Coast 10-digit mobile" }],
bj: [{ number: "+2290165526657", description: "Benin 10-digit mobile" }],
at: [{ number: "+436641234567", description: "Austria mobile" }],
ar: [
{ number: "+5491112345678", description: "Argentina mobile (Buenos Aires, 11 national digits)" },
{ number: "+5493414123456", description: "Argentina mobile (Rosario, 11 national digits)" },
{ number: "+541112345678", description: "Argentina landline (10 national digits)" },
],
fi: [
{ number: "+3584012345678", description: "Finland 10-digit mobile" },
{ number: "+358501234567", description: "Finland 9-digit mobile" },
],
};
describe("CUSTOM_PHONE_MASKS", () => {
describe.each(Object.entries(CUSTOM_PHONE_MASKS))("mask for %s", (countryCode, mask) => {
const maskDigits = countMaskDigits(mask);
const testNumbers = COUNTRY_TEST_NUMBERS[countryCode] || [];
it("should have enough digit placeholders for all valid phone numbers", () => {
for (const { number, description } of testNumbers) {
const parsed = parsePhoneNumberFromString(number);
expect(parsed, `Failed to parse ${description}: ${number}`).toBeTruthy();
const nationalDigits = parsed?.nationalNumber.length ?? 0;
expect(
maskDigits,
`Mask for ${countryCode} has ${maskDigits} digit slots but ${description} (${number}) needs ${nationalDigits} national digits`
).toBeGreaterThanOrEqual(nationalDigits);
}
});
it("should have test numbers that pass libphonenumber-js validation", () => {
for (const { number, description } of testNumbers) {
expect(isValidPhoneNumber(number), `${description} (${number}) should be a valid phone number`).toBe(
true
);
}
});
});
});
@@ -2,20 +2,15 @@
import { isSupportedCountry } from "libphonenumber-js";
import type { CSSProperties } from "react";
import { useState, useEffect } from "react";
import { useEffect, useState } from "react";
import PhoneInput from "react-phone-input-2";
import "react-phone-input-2/lib/style.css";
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
import { useBookerStore, type CountryCode } from "@calcom/features/bookings/Booker/store";
import { type CountryCode, useBookerStore } from "@calcom/features/bookings/Booker/store";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
const CUSTOM_PHONE_MASKS = {
ci: ".. .. .. .. ..",
bj: ".. .. .. .. ..",
at: "... ..........",
};
import { CUSTOM_PHONE_MASKS } from "./phone-masks";
export type PhoneInputProps = {
value?: string;
@@ -0,0 +1,20 @@
/**
* Custom phone masks to override outdated masks in react-phone-input-2.
* The library's built-in masks can lag behind countries' numbering plan changes,
* causing valid phone numbers to be truncated (users can't type enough digits).
*
* Each dot (.) represents a digit placeholder. When a country updates its
* numbering plan to use longer numbers, add an override here.
*/
export const CUSTOM_PHONE_MASKS = {
/** Ivory Coast: migrated from 8 to 10 digits in 2021 */
ci: ".. .. .. .. ..",
/** Benin: migrated from 8 to 10 digits in 2025 */
bj: ".. .. .. .. ..",
/** Austria: variable-length numbers up to 13 digits */
at: "... ..........",
/** Argentina: mobile numbers require 11 national digits (9 + area code + subscriber) */
ar: "(..) .........",
/** Finland: some mobile numbers use 10 national digits */
fi: ".. ... .. ...",
};