Skip to content

Commit 2bf4567

Browse files
devin-ai-integration[bot]susanatcalcombot_apkeunjae-leeUdit-takkar
authored
fix: add phone mask overrides for Argentina and Finland to prevent digit truncation (calcom#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>
1 parent 1b21ead commit 2bf4567

3 files changed

Lines changed: 76 additions & 8 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { isValidPhoneNumber, parsePhoneNumberFromString } from "libphonenumber-js/max";
2+
import { describe, expect, it } from "vitest";
3+
import { CUSTOM_PHONE_MASKS } from "./phone-masks";
4+
5+
/** Count the digit placeholders (dots) in a react-phone-input-2 mask */
6+
const countMaskDigits = (mask: string): number => (mask.match(/\./g) || []).length;
7+
8+
/**
9+
* Test phone numbers that must be accepted for each country with a custom mask.
10+
* These are realistic mobile and landline numbers validated by libphonenumber-js.
11+
*/
12+
const COUNTRY_TEST_NUMBERS: Record<string, { number: string; description: string }[]> = {
13+
ci: [{ number: "+2250797764877", description: "Ivory Coast 10-digit mobile" }],
14+
bj: [{ number: "+2290165526657", description: "Benin 10-digit mobile" }],
15+
at: [{ number: "+436641234567", description: "Austria mobile" }],
16+
ar: [
17+
{ number: "+5491112345678", description: "Argentina mobile (Buenos Aires, 11 national digits)" },
18+
{ number: "+5493414123456", description: "Argentina mobile (Rosario, 11 national digits)" },
19+
{ number: "+541112345678", description: "Argentina landline (10 national digits)" },
20+
],
21+
fi: [
22+
{ number: "+3584012345678", description: "Finland 10-digit mobile" },
23+
{ number: "+358501234567", description: "Finland 9-digit mobile" },
24+
],
25+
};
26+
27+
describe("CUSTOM_PHONE_MASKS", () => {
28+
describe.each(Object.entries(CUSTOM_PHONE_MASKS))("mask for %s", (countryCode, mask) => {
29+
const maskDigits = countMaskDigits(mask);
30+
const testNumbers = COUNTRY_TEST_NUMBERS[countryCode] || [];
31+
32+
it("should have enough digit placeholders for all valid phone numbers", () => {
33+
for (const { number, description } of testNumbers) {
34+
const parsed = parsePhoneNumberFromString(number);
35+
expect(parsed, `Failed to parse ${description}: ${number}`).toBeTruthy();
36+
37+
const nationalDigits = parsed?.nationalNumber.length ?? 0;
38+
expect(
39+
maskDigits,
40+
`Mask for ${countryCode} has ${maskDigits} digit slots but ${description} (${number}) needs ${nationalDigits} national digits`
41+
).toBeGreaterThanOrEqual(nationalDigits);
42+
}
43+
});
44+
45+
it("should have test numbers that pass libphonenumber-js validation", () => {
46+
for (const { number, description } of testNumbers) {
47+
expect(isValidPhoneNumber(number), `${description} (${number}) should be a valid phone number`).toBe(
48+
true
49+
);
50+
}
51+
});
52+
});
53+
});

apps/web/components/phone-input/PhoneInput.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,15 @@
22

33
import { isSupportedCountry } from "libphonenumber-js";
44
import type { CSSProperties } from "react";
5-
import { useState, useEffect } from "react";
5+
import { useEffect, useState } from "react";
66
import PhoneInput from "react-phone-input-2";
77
import "react-phone-input-2/lib/style.css";
88

99
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
10-
import { useBookerStore, type CountryCode } from "@calcom/features/bookings/Booker/store";
10+
import { type CountryCode, useBookerStore } from "@calcom/features/bookings/Booker/store";
1111
import { trpc } from "@calcom/trpc/react";
1212
import classNames from "@calcom/ui/classNames";
13-
14-
const CUSTOM_PHONE_MASKS = {
15-
ci: ".. .. .. .. ..",
16-
bj: ".. .. .. .. ..",
17-
at: "... ..........",
18-
};
13+
import { CUSTOM_PHONE_MASKS } from "./phone-masks";
1914

2015
export type PhoneInputProps = {
2116
value?: string;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Custom phone masks to override outdated masks in react-phone-input-2.
3+
* The library's built-in masks can lag behind countries' numbering plan changes,
4+
* causing valid phone numbers to be truncated (users can't type enough digits).
5+
*
6+
* Each dot (.) represents a digit placeholder. When a country updates its
7+
* numbering plan to use longer numbers, add an override here.
8+
*/
9+
export const CUSTOM_PHONE_MASKS = {
10+
/** Ivory Coast: migrated from 8 to 10 digits in 2021 */
11+
ci: ".. .. .. .. ..",
12+
/** Benin: migrated from 8 to 10 digits in 2025 */
13+
bj: ".. .. .. .. ..",
14+
/** Austria: variable-length numbers up to 13 digits */
15+
at: "... ..........",
16+
/** Argentina: mobile numbers require 11 national digits (9 + area code + subscriber) */
17+
ar: "(..) .........",
18+
/** Finland: some mobile numbers use 10 national digits */
19+
fi: ".. ... .. ...",
20+
};

0 commit comments

Comments
 (0)