Skip to content

Commit 61faff1

Browse files
fix: improve country code detection for phone input
1 parent dc43eba commit 61faff1

3 files changed

Lines changed: 88 additions & 5 deletions

File tree

apps/web/app/api/geolocation/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NextResponse } from "next/server";
44

55
async function getHandler() {
66
const headersList = await headers();
7-
const country = headersList.get("x-vercel-ip-country") || "Unknown";
7+
const country = headersList.get("cf-ipcountry") || headersList.get("x-vercel-ip-country") || "Unknown";
88

99
const response = NextResponse.json({ country });
1010
response.headers.set("Cache-Control", "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400");

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

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,88 @@ function BasePhoneInputWeb({
155155
);
156156
}
157157

158+
// Maps IANA timezone to ISO 3166-1 alpha-2 country code (lowercase).
159+
// navigator.language region is unreliable — browser language ≠ user location.
160+
// Timezone is a much better proxy for physical location.
161+
const TIMEZONE_COUNTRY_MAP: Record<string, string> = {
162+
"Asia/Kolkata": "in",
163+
"Asia/Calcutta": "in",
164+
"America/New_York": "us",
165+
"America/Chicago": "us",
166+
"America/Denver": "us",
167+
"America/Los_Angeles": "us",
168+
"America/Anchorage": "us",
169+
"Pacific/Honolulu": "us",
170+
"Europe/London": "gb",
171+
"Europe/Paris": "fr",
172+
"Europe/Berlin": "de",
173+
"Europe/Madrid": "es",
174+
"Europe/Rome": "it",
175+
"Europe/Amsterdam": "nl",
176+
"Europe/Brussels": "be",
177+
"Europe/Zurich": "ch",
178+
"Europe/Vienna": "at",
179+
"Europe/Stockholm": "se",
180+
"Europe/Oslo": "no",
181+
"Europe/Copenhagen": "dk",
182+
"Europe/Helsinki": "fi",
183+
"Europe/Warsaw": "pl",
184+
"Europe/Prague": "cz",
185+
"Europe/Bucharest": "ro",
186+
"Europe/Athens": "gr",
187+
"Europe/Istanbul": "tr",
188+
"Europe/Moscow": "ru",
189+
"Europe/Lisbon": "pt",
190+
"Europe/Dublin": "ie",
191+
"Asia/Tokyo": "jp",
192+
"Asia/Shanghai": "cn",
193+
"Asia/Hong_Kong": "hk",
194+
"Asia/Singapore": "sg",
195+
"Asia/Seoul": "kr",
196+
"Asia/Taipei": "tw",
197+
"Asia/Bangkok": "th",
198+
"Asia/Jakarta": "id",
199+
"Asia/Manila": "ph",
200+
"Asia/Kuala_Lumpur": "my",
201+
"Asia/Dubai": "ae",
202+
"Asia/Riyadh": "sa",
203+
"Asia/Karachi": "pk",
204+
"Asia/Dhaka": "bd",
205+
"Asia/Colombo": "lk",
206+
"Asia/Kathmandu": "np",
207+
"Asia/Ho_Chi_Minh": "vn",
208+
"Australia/Sydney": "au",
209+
"Australia/Melbourne": "au",
210+
"Australia/Perth": "au",
211+
"Pacific/Auckland": "nz",
212+
"America/Toronto": "ca",
213+
"America/Vancouver": "ca",
214+
"America/Mexico_City": "mx",
215+
"America/Sao_Paulo": "br",
216+
"America/Argentina/Buenos_Aires": "ar",
217+
"America/Santiago": "cl",
218+
"America/Bogota": "co",
219+
"America/Lima": "pe",
220+
"Africa/Cairo": "eg",
221+
"Africa/Lagos": "ng",
222+
"Africa/Johannesburg": "za",
223+
"Africa/Nairobi": "ke",
224+
"Africa/Casablanca": "ma",
225+
"Asia/Jerusalem": "il",
226+
"Asia/Beirut": "lb",
227+
"Asia/Baghdad": "iq",
228+
"Asia/Tehran": "ir",
229+
};
230+
231+
function getCountryFromTimezone(): string | undefined {
232+
try {
233+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
234+
return TIMEZONE_COUNTRY_MAP[tz];
235+
} catch {
236+
return undefined;
237+
}
238+
}
239+
158240
const useDefaultCountry = () => {
159241
const defaultPhoneCountryFromStore = useBookerStore((state) => state.defaultPhoneCountry);
160242
const [defaultCountry, setDefaultCountry] = useState<CountryCode>(defaultPhoneCountryFromStore || "us");
@@ -179,9 +261,9 @@ const useDefaultCountry = () => {
179261
if (isSupportedCountry(data?.countryCode)) {
180262
setDefaultCountry(data.countryCode.toLowerCase() as CountryCode);
181263
} else {
182-
const navCountry = navigator.language.split("-")[1]?.toUpperCase();
183-
if (navCountry && isSupportedCountry(navCountry)) {
184-
setDefaultCountry(navCountry.toLowerCase() as CountryCode);
264+
const tzCountry = getCountryFromTimezone();
265+
if (tzCountry && isSupportedCountry(tzCountry.toUpperCase())) {
266+
setDefaultCountry(tzCountry as CountryCode);
185267
} else {
186268
setDefaultCountry("us");
187269
}

packages/trpc/server/routers/publicViewer/countryCode.handler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ type CountryCodeOptions = {
77
export const countryCodeHandler = async ({ ctx }: CountryCodeOptions) => {
88
const { req } = ctx;
99

10-
const countryCode: string | string[] = req?.headers?.["x-vercel-ip-country"] ?? "";
10+
const countryCode: string | string[] =
11+
req?.headers?.["cf-ipcountry"] || req?.headers?.["x-vercel-ip-country"] || "";
1112
return { countryCode: Array.isArray(countryCode) ? countryCode[0] : countryCode };
1213
};
1314

0 commit comments

Comments
 (0)