Skip to content

Commit 037cb8e

Browse files
ThyMinimalDevdevin-ai-integration[bot]Ryukemeister
authored
feat: add defaultPhoneCountry prop with ISO 3166-1 alpha-2 type safety (calcom#25204)
* feat: add defaultPhoneCountry prop to BookerPlatformWrapper - Add defaultPhoneCountry to BookerStore type and implementation - Add defaultPhoneCountry prop to BookerPlatformWrapper types - Pass defaultPhoneCountry through store initialization - Update PhoneInput to use defaultPhoneCountry from store - Support default phone country extension for phone inputs in booker form * feat: add strict typing for defaultPhoneCountry with ISO 3166-1 alpha-2 codes - Define CountryCode type using ISO 3166-1 alpha-2 country codes - Update defaultPhoneCountry prop type in BookerPlatformWrapper to use CountryCode - Update defaultPhoneCountry type in BookerStore to use CountryCode - Ensures type safety by only allowing valid country codes like 'us', 'gb', 'ee', etc. - Fix lint warnings by prefixing type-only constants with underscore Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: export CountryCode from store to avoid duplication - Export CountryCode type from packages/features/bookings/Booker/store.ts - Import CountryCode in packages/platform/atoms/booker/types.ts from store - Remove duplicate CountryCode definition from types.ts - Maintains single source of truth for country code type definition Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: add type-safe casts for CountryCode in PhoneInput - Import CountryCode type from store - Add explicit type annotation to useState<CountryCode> - Add safe type casts with isSupportedCountry validation - Validate navigator.language country code before using it - Fixes CI type error: string not assignable to CountryCode Co-Authored-By: morgan@cal.com <morgan@cal.com> * docs: add defaultPhoneCountry prop documentation and changeset - Add defaultPhoneCountry prop to booker.mdx documentation - Add changeset for minor version bump - Document ISO 3166-1 alpha-2 country code support Co-Authored-By: morgan@cal.com <morgan@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
1 parent f0e1012 commit 037cb8e

6 files changed

Lines changed: 73 additions & 9 deletions

File tree

.changeset/little-rings-repeat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@calcom/atoms": minor
3+
---
4+
5+
Add defaultPhoneCountry prop to BookerPlatformWrapper with ISO 3166-1 alpha-2 type safety

docs/platform/atoms/booker.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Below is a list of props that can be passed to the booker atom.
131131
| showNoAvailabilityDialog | No | Boolean indicating if the no availability dialog should be shown, defaults to true. |
132132
| silentlyHandleCalendarFailures | No | Boolean when true the booker still displays slots when the third party calendars credentials are invalid or expired, Booker may show stale availability when enabled |
133133
| hideEventMetadata | No | Boolean that controls the visibility of the event metadata sidebar. When `true`, hides the left sidebar containing event details like title, description, duration, and host information. Defaults to `false`. |
134+
| defaultPhoneCountry | No | Sets the default country code for phone number inputs in the booking form. Accepts ISO 3166-1 alpha-2 country codes (e.g., `"us"`, `"gb"`, `"in"`, `"ee"`). When set, phone inputs will default to the specified country's dialing code. |
134135

135136
## Styling
136137

packages/features/bookings/Booker/store.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,36 @@ import type { GetBookingType } from "../lib/get-booking";
1515
import type { BookerState, BookerLayout } from "./types";
1616
import { updateQueryParam, getQueryParam, removeQueryParam } from "./utils/query-param";
1717

18+
const _iso_3166_1_alpha_2_codes = [
19+
"ad", "ae", "af", "ag", "ai", "al", "am", "ao", "aq", "ar", "as", "at", "au", "aw", "ax", "az",
20+
"ba", "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", "bl", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by", "bz",
21+
"ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cw", "cx", "cy", "cz",
22+
"de", "dj", "dk", "dm", "do", "dz",
23+
"ec", "ee", "eg", "eh", "er", "es", "et",
24+
"fi", "fj", "fk", "fm", "fo", "fr",
25+
"ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr", "gs", "gt", "gu", "gw", "gy",
26+
"hk", "hm", "hn", "hr", "ht", "hu",
27+
"id", "ie", "il", "im", "in", "io", "iq", "ir", "is", "it",
28+
"je", "jm", "jo", "jp",
29+
"ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz",
30+
"la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly",
31+
"ma", "mc", "md", "me", "mf", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz",
32+
"na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz",
33+
"om",
34+
"pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", "pr", "ps", "pt", "pw", "py",
35+
"qa",
36+
"re", "ro", "rs", "ru", "rw",
37+
"sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "sv", "sx", "sy",
38+
"tc", "td", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz",
39+
"ua", "ug", "um", "us", "uy", "uz",
40+
"va", "vc", "ve", "vg", "vi", "vn", "vu",
41+
"wf", "ws",
42+
"ye", "yt",
43+
"za", "zm", "zw"
44+
] as const;
45+
46+
export type CountryCode = typeof _iso_3166_1_alpha_2_codes[number];
47+
1848

1949
/**
2050
* Arguments passed into store initializer, containing
@@ -44,6 +74,7 @@ export type StoreInitializeType = {
4474
crmRecordId?: string | null;
4575
isPlatform?: boolean;
4676
allowUpdatingUrlParams?: boolean;
77+
defaultPhoneCountry?: CountryCode;
4778
};
4879

4980
type SeatedEventData = {
@@ -183,6 +214,7 @@ export type BookerStore = {
183214
crmRecordId?: string | null;
184215
isPlatform?: boolean;
185216
allowUpdatingUrlParams?: boolean;
217+
defaultPhoneCountry?: CountryCode | null;
186218
};
187219

188220
/**
@@ -329,6 +361,7 @@ export const createBookerStore = () =>
329361
crmRecordId,
330362
isPlatform = false,
331363
allowUpdatingUrlParams = true,
364+
defaultPhoneCountry,
332365
}: StoreInitializeType) => {
333366
const selectedDateInStore = get().selectedDate;
334367

@@ -372,6 +405,7 @@ export const createBookerStore = () =>
372405
crmRecordId,
373406
isPlatform,
374407
allowUpdatingUrlParams,
408+
defaultPhoneCountry,
375409
});
376410

377411
if (durationConfig?.includes(Number(getQueryParam("duration")))) {
@@ -461,6 +495,7 @@ export const createBookerStore = () =>
461495
},
462496
isPlatform: false,
463497
allowUpdatingUrlParams: true,
498+
defaultPhoneCountry: null,
464499
}));
465500

466501
/**
@@ -489,6 +524,7 @@ export const useInitializeBookerStore = ({
489524
crmRecordId,
490525
isPlatform = false,
491526
allowUpdatingUrlParams = true,
527+
defaultPhoneCountry,
492528
}: StoreInitializeType) => {
493529
const initializeStore = useBookerStore((state) => state.initialize);
494530
useEffect(() => {
@@ -513,6 +549,7 @@ export const useInitializeBookerStore = ({
513549
crmRecordId,
514550
isPlatform,
515551
allowUpdatingUrlParams,
552+
defaultPhoneCountry,
516553
});
517554
}, [
518555
initializeStore,
@@ -536,5 +573,6 @@ export const useInitializeBookerStore = ({
536573
crmRecordId,
537574
isPlatform,
538575
allowUpdatingUrlParams,
576+
defaultPhoneCountry,
539577
]);
540-
};
578+
};

packages/features/components/phone-input/PhoneInput.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ 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";
1011
import { trpc } from "@calcom/trpc/react";
1112
import classNames from "@calcom/ui/classNames";
1213

@@ -33,6 +34,8 @@ function BasePhoneInput({
3334
...rest
3435
}: PhoneInputProps) {
3536
const isPlatform = useIsPlatform();
37+
const defaultPhoneCountryFromStore = useBookerStore((state) => state.defaultPhoneCountry);
38+
const effectiveDefaultCountry = defaultPhoneCountryFromStore || defaultCountry;
3639

3740
// This is to trigger validation on prefill value changes
3841
useEffect(() => {
@@ -63,7 +66,7 @@ function BasePhoneInput({
6366
value={value ? value.trim().replace(/^\+?/, "+") : undefined}
6467
enableSearch
6568
disableSearchIcon
66-
country={defaultCountry}
69+
country={effectiveDefaultCountry}
6770
inputProps={{
6871
name,
6972
required: rest.required,
@@ -150,7 +153,8 @@ function BasePhoneInputWeb({
150153
}
151154

152155
const useDefaultCountry = () => {
153-
const [defaultCountry, setDefaultCountry] = useState("us");
156+
const defaultPhoneCountryFromStore = useBookerStore((state) => state.defaultPhoneCountry);
157+
const [defaultCountry, setDefaultCountry] = useState<CountryCode>(defaultPhoneCountryFromStore || "us");
154158
const query = trpc.viewer.public.countryCode.useQuery(undefined, {
155159
refetchOnWindowFocus: false,
156160
refetchOnReconnect: false,
@@ -159,16 +163,28 @@ const useDefaultCountry = () => {
159163

160164
useEffect(
161165
function refactorMeWithoutEffect() {
166+
if (defaultPhoneCountryFromStore) {
167+
setDefaultCountry(defaultPhoneCountryFromStore);
168+
return;
169+
}
170+
162171
const data = query.data;
163172
if (!data?.countryCode) {
164173
return;
165174
}
166175

167-
isSupportedCountry(data?.countryCode)
168-
? setDefaultCountry(data.countryCode.toLowerCase())
169-
: setDefaultCountry(navigator.language.split("-")[1]?.toLowerCase() || "us");
176+
if (isSupportedCountry(data?.countryCode)) {
177+
setDefaultCountry(data.countryCode.toLowerCase() as CountryCode);
178+
} else {
179+
const navCountry = navigator.language.split("-")[1]?.toUpperCase();
180+
if (navCountry && isSupportedCountry(navCountry)) {
181+
setDefaultCountry(navCountry.toLowerCase() as CountryCode);
182+
} else {
183+
setDefaultCountry("us");
184+
}
185+
}
170186
},
171-
[query.data]
187+
[query.data, defaultPhoneCountryFromStore]
172188
);
173189

174190
return defaultCountry;

packages/platform/atoms/booker/BookerPlatformWrapper.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const BookerPlatformWrapperComponent = (
7070
showNoAvailabilityDialog,
7171
silentlyHandleCalendarFailures = false,
7272
hideEventMetadata = false,
73+
defaultPhoneCountry,
7374
} = props;
7475
const layout = BookerLayouts[view];
7576

@@ -185,6 +186,7 @@ const BookerPlatformWrapperComponent = (
185186
bookingData,
186187
isPlatform: true,
187188
allowUpdatingUrlParams,
189+
defaultPhoneCountry,
188190
});
189191
useInitializeBookerStoreContext({
190192
...props,
@@ -201,6 +203,7 @@ const BookerPlatformWrapperComponent = (
201203
bookingData,
202204
isPlatform: true,
203205
allowUpdatingUrlParams,
206+
defaultPhoneCountry,
204207
});
205208
const [dayCount] = useBookerStoreContext((state) => [state.dayCount, state.setDayCount], shallow);
206209
const selectedDate = useBookerStoreContext((state) => state.selectedDate);

packages/platform/atoms/booker/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type React from "react";
33

44

55
import type { BookerProps } from "@calcom/features/bookings/Booker";
6-
import type { BookerStore } from "@calcom/features/bookings/Booker/store";
6+
import type { BookerStore, CountryCode } from "@calcom/features/bookings/Booker/store";
77
import type { Timezone, VIEW_TYPE } from "@calcom/features/bookings/Booker/types";
88
import type { BookingCreateBody } from "@calcom/features/bookings/lib/bookingCreateBodySchema";
99
import type { BookingResponse } from "@calcom/platform-libraries";
@@ -88,6 +88,7 @@ export type BookerPlatformWrapperAtomProps = Omit<
8888
roundRobinHideOrgAndTeam?: boolean;
8989
silentlyHandleCalendarFailures?: boolean;
9090
hideEventMetadata?: boolean;
91+
defaultPhoneCountry?: CountryCode;
9192
};
9293

9394
export type BookerPlatformWrapperAtomPropsForIndividual = BookerPlatformWrapperAtomProps & {
@@ -101,4 +102,4 @@ export type BookerPlatformWrapperAtomPropsForTeam = BookerPlatformWrapperAtomPro
101102
isTeamEvent: true;
102103
teamId: number;
103104
routingFormSearchParams?: RoutingFormSearchParams;
104-
};
105+
};

0 commit comments

Comments
 (0)