From 73758ec53fa9e0798a9e5ddb8ff4d49c7c7386af Mon Sep 17 00:00:00 2001 From: mortezaei Date: Thu, 20 Aug 2026 17:19:27 +0330 Subject: [PATCH] refactor: improve geo-location handling by standardizing country name resolution and forwarding client IP headers to the backend --- src/app/api/proxy/route.ts | 27 ++++++++++ .../Componentes/question-birthplace.tsx | 45 ++++++++++------- src/data/countries.ts | 49 +++++++++++++++++++ src/lib/geo-region.ts | 14 ++++-- 4 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/app/api/proxy/route.ts b/src/app/api/proxy/route.ts index 573258b..3c04afd 100644 --- a/src/app/api/proxy/route.ts +++ b/src/app/api/proxy/route.ts @@ -17,6 +17,14 @@ const REQUEST_HEADERS_TO_FORWARD = [ "x-csrftoken", "x-requested-with", "x-xsrf-token", + "cf-connecting-ip", + "cf-ipcountry", + "cf-ray", + "cf-visitor", + "x-real-ip", + "x-forwarded-for", + "x-forwarded-proto", + "x-forwarded-host", ]; const RESPONSE_HEADERS_TO_DROP = [ @@ -166,6 +174,25 @@ function getRequestHeaders(request: NextRequest, targetUrl: URL) { headers.set("referer", `${targetUrl.origin}/`); headers.set("user-agent", "dart:io"); + // Forward real client IP and Geo headers to Django backend + const clientIp = + request.headers.get("cf-connecting-ip") || + request.headers.get("x-real-ip") || + request.headers.get("x-forwarded-for")?.split(",")[0]?.trim(); + + if (clientIp) { + headers.set("cf-connecting-ip", clientIp); + headers.set("x-real-ip", clientIp); + if (!headers.has("x-forwarded-for")) { + headers.set("x-forwarded-for", clientIp); + } + } + + const cfCountry = request.headers.get("cf-ipcountry"); + if (cfCountry) { + headers.set("cf-ipcountry", cfCountry); + } + return headers; } diff --git a/src/components/Componentes/question-birthplace.tsx b/src/components/Componentes/question-birthplace.tsx index e9b167a..1c92e4e 100644 --- a/src/components/Componentes/question-birthplace.tsx +++ b/src/components/Componentes/question-birthplace.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; -import { getCountryList, COUNTRIES_EN, COUNTRIES_FA } from "@/data/countries"; +import { getCountryList, resolveCountryName, COUNTRIES_EN, COUNTRIES_FA } from "@/data/countries"; import type { QuestionField } from "@/lib/schema-adapter"; import { useI18n } from "@/translations/provider"; import { useQuestionAnswers } from "./question-answer-storage"; @@ -93,18 +93,21 @@ export function QuestionBirthplace({ const storedRegion = isResidence ? getStoredUserGeoRegion() : null; const initial = parseValue(rawValue); + const localizedInitialCountry = resolveCountryName( + initial.country || storedRegion?.country, + locale, + ); + const initialCity = initial.city || storedRegion?.city || ""; const initialLoc = - initial.country || initial.city - ? [initial.city, initial.country].filter(Boolean).join(", ") - : storedRegion?.city || storedRegion?.country - ? [storedRegion.city, storedRegion.country].filter(Boolean).join(", ") - : ""; + localizedInitialCountry || initialCity + ? [initialCity, localizedInitialCountry].filter(Boolean).join(", ") + : ""; const [selectedCountry, setSelectedCountry] = useState( - () => initial.country || storedRegion?.country || "", + () => localizedInitialCountry || "", ); const [cityInput, setCityInput] = useState( - () => initial.city || storedRegion?.city || "", + () => initialCity, ); const [isOpen, setIsOpen] = useState(false); @@ -170,8 +173,11 @@ export function QuestionBirthplace({ // If there is already a saved answer and we are not forcing, display it if (rawValue && !force) { const parsed = parseValue(rawValue); - if (parsed.country || parsed.city) { - const loc = [parsed.city, parsed.country].filter(Boolean).join(", "); + const cName = resolveCountryName(parsed.country, locale); + if (cName || parsed.city) { + const loc = [parsed.city, cName].filter(Boolean).join(", "); + setSelectedCountry(cName); + setCityInput(parsed.city); setDetectedLocation(loc); setMode("auto"); return; @@ -185,7 +191,8 @@ export function QuestionBirthplace({ if (!isMountedRef.current) return; const city = region.city || ""; - const country = region.country || ""; + const rawCountry = region.country || region.countryCode || ""; + const country = resolveCountryName(rawCountry, locale) || rawCountry; if (city || country) { const loc = [city, country].filter(Boolean).join(", "); @@ -206,7 +213,7 @@ export function QuestionBirthplace({ setIsDetecting(false); } } - }, [rawValue]); + }, [rawValue, locale]); useEffect(() => { if (isLoading) return; @@ -226,7 +233,8 @@ export function QuestionBirthplace({ } setMode("manual"); const parsed = parseValue(rawValue); - const country = selectedCountry || parsed.country; + const resolvedC = resolveCountryName(selectedCountry || parsed.country, locale); + const country = resolvedC || selectedCountry || parsed.country; const city = cityInput || parsed.city; setSelectedCountry(country); setCityInput(city); @@ -236,16 +244,17 @@ export function QuestionBirthplace({ // Synchronize state if rawValue changes externally useEffect(() => { const updated = parseValue(rawValue); - if (updated.country !== selectedCountry) { - setSelectedCountry(updated.country); + const resolvedC = resolveCountryName(updated.country, locale); + if (resolvedC && resolvedC !== selectedCountry) { + setSelectedCountry(resolvedC); } if (updated.city.trim() !== cityInput.trim()) { setCityInput(updated.city); } - if (updated.country && updated.city) { - setDetectedLocation(`${updated.city}, ${updated.country}`); + if (resolvedC || updated.city) { + setDetectedLocation([updated.city, resolvedC].filter(Boolean).join(", ")); } - }, [rawValue]); + }, [rawValue, locale]); const options = getCountryList(locale); const filteredOptions = options.filter((option) => diff --git a/src/data/countries.ts b/src/data/countries.ts index c429779..fa74570 100644 --- a/src/data/countries.ts +++ b/src/data/countries.ts @@ -399,3 +399,52 @@ export function getCountryList(locale: string): string[] { } return COUNTRIES_EN; } + +export function resolveCountryName( + countryOrCode: string | undefined | null, + locale: string = "fa", +): string { + if (!countryOrCode) return ""; + const trimmed = countryOrCode.trim(); + if (!trimmed) return ""; + + const isFa = String(locale || "en").toLowerCase().startsWith("fa"); + + // 1. If 2-letter ISO code (e.g. "DE", "IR", "US", "TJ") + if (trimmed.length === 2 && /^[a-zA-Z]{2}$/.test(trimmed)) { + try { + const displayNames = new Intl.DisplayNames([isFa ? "fa" : "en"], { + type: "region", + }); + const name = displayNames.of(trimmed.toUpperCase()); + if (name) return name; + } catch {} + } + + // 2. If it matches an English country name in COUNTRIES_EN + const enIndex = COUNTRIES_EN.findIndex( + (c) => c.toLowerCase() === trimmed.toLowerCase(), + ); + if (enIndex !== -1) { + return isFa ? COUNTRIES_FA[enIndex] || COUNTRIES_EN[enIndex] : COUNTRIES_EN[enIndex]; + } + + // 3. If it matches a Persian country name in COUNTRIES_FA + const faIndex = COUNTRIES_FA.findIndex((c) => c === trimmed); + if (faIndex !== -1) { + return isFa ? COUNTRIES_FA[faIndex] : COUNTRIES_EN[faIndex] || COUNTRIES_FA[faIndex]; + } + + // 4. Try Intl.DisplayNames fallback if 2-3 chars + if (trimmed.length <= 3) { + try { + const displayNames = new Intl.DisplayNames([isFa ? "fa" : "en"], { + type: "region", + }); + const name = displayNames.of(trimmed.toUpperCase()); + if (name) return name; + } catch {} + } + + return trimmed; +} diff --git a/src/lib/geo-region.ts b/src/lib/geo-region.ts index 72493a4..93026a3 100644 --- a/src/lib/geo-region.ts +++ b/src/lib/geo-region.ts @@ -2,6 +2,7 @@ import { PhoneNumberUtil } from "google-libphonenumber"; import { http } from "./http"; +import { resolveCountryName } from "@/data/countries"; export type UserGeoRegion = { ip?: string; @@ -103,12 +104,19 @@ export function getUserGeoRegion(): Promise { const data = response.data; if (data && (data.country || data.country_code || data.city)) { - const phoneCode = resolvePhoneCodeFromCountryCode(data.country_code); + const isoCode = + data.country_code || + (data.country && data.country.trim().length === 2 + ? data.country.trim().toUpperCase() + : undefined); + const phoneCode = resolvePhoneCodeFromCountryCode(isoCode); + const countryName = + resolveCountryName(data.country || isoCode, "en") || data.country; const region: UserGeoRegion = { ip: data.ip, city: data.city, - country: data.country, - countryCode: data.country_code, + country: countryName, + countryCode: isoCode, phoneCode: phoneCode || "+44", }; setStoredUserGeoRegion(region);