"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { getCountryList, resolveCountryName, isKnownCountry, } from "@/data/countries"; import type { QuestionField } from "@/lib/schema-adapter"; import { useI18n } from "@/translations/provider"; import { useQuestionAnswers } from "./question-answer-storage"; import QuestionTitle from "./question-title"; import { LoadingThreeDot } from "./loading-three-dot"; import { useSheetScrollLock } from "./use-sheet-scroll-lock"; import { Input } from "@/components/ui/input"; import { getUserGeoRegion, getStoredUserGeoRegion, subscribeToUserGeoRegion, } from "@/lib/geo-region"; import { isInFlutterWebView, requestAutoLocation, pickManualLocation, } from "@/lib/webview-actions"; const EXIT_ANIMATION_MS = 220; type QuestionBirthplaceProps = { question: QuestionField; disabled?: boolean; }; type BirthplaceValue = { country?: string; city?: string; }; export function parseValue(rawValue: unknown): { country: string; city: string; } { if (!rawValue) return { country: "", city: "" }; if (typeof rawValue === "object" && rawValue !== null) { const obj = rawValue as BirthplaceValue; const rawCountry = typeof obj.country === "string" ? obj.country.trim() : ""; const rawCity = typeof obj.city === "string" ? obj.city.trim() : ""; // If obj has country and city inverted (e.g. { country: "Mashhad", city: "Iran" }) if ( rawCountry && rawCity && !isKnownCountry(rawCountry) && isKnownCountry(rawCity) ) { return { country: rawCity, city: rawCountry, }; } // If only country is provided but it is actually a city if (rawCountry && !rawCity && !isKnownCountry(rawCountry)) { return { country: "", city: rawCountry, }; } // If only city is provided but it is actually a country if (!rawCountry && rawCity && isKnownCountry(rawCity)) { return { country: rawCity, city: "", }; } return { country: rawCountry, city: rawCity, }; } if (typeof rawValue === "string") { let str = rawValue.trim(); if (!str) return { country: "", city: "" }; // Clean legacy country names with parentheses containing commas str = str.replace( "United Kingdom (UK, England, Wales, Scotland, Northern Ireland)", "United Kingdom (UK)", ); str = str.replace("United States (US, USA, America)", "United States (US)"); str = str.replace( "United Arab Emirates (UAE, Dubai, Abu Dhabi)", "United Arab Emirates (UAE)", ); str = str.replace("Congo (Congo-Brazzaville)", "Congo"); str = str.replace("Czechia (Czech Republic)", "Czechia"); str = str.replace("Myanmar (formerly Burma)", "Myanmar"); str = str.replace( "امارات متحده عربی (دبی، ابوظبی، UAE)", "امارات متحده عربی (UAE)", ); str = str.replace( "بریتانیا (انگلستان، اسکاتلند، ولز، ایرلند شمالی، UK)", "بریتانیا (UK)", ); str = str.replace( "ایالات متحده آمریکا (آمریکا، US، USA)", "ایالات متحده آمریکا (US)", ); const splitLocation = (part1: string, part2: string) => { // 1. If part1 is a known country and part2 is not (or both), part1 is country, part2 is city if (isKnownCountry(part1)) { return { country: part1, city: part2 }; } // 2. If part1 is not a known country, but part2 is a known country (e.g. "Mashhad, Iran" or "مشهد، ایران") if (isKnownCountry(part2)) { return { country: part2, city: part1 }; } // 3. Fallback: assume first part is country return { country: part1, city: part2 }; }; if (str.includes(",")) { const idx = str.indexOf(","); return splitLocation(str.slice(0, idx).trim(), str.slice(idx + 1).trim()); } if (str.includes("،")) { const idx = str.indexOf("،"); return splitLocation(str.slice(0, idx).trim(), str.slice(idx + 1).trim()); } if (str.includes(" - ")) { const idx = str.indexOf(" - "); return splitLocation(str.slice(0, idx).trim(), str.slice(idx + 3).trim()); } if (isKnownCountry(str)) { return { country: str, city: "" }; } return { country: "", city: str }; } return { country: "", city: "" }; } export function QuestionBirthplace({ question, disabled, }: QuestionBirthplaceProps) { const { dictionary: t, locale } = useI18n(); const { getAnswerValue, setAnswerValue, isLoading } = useQuestionAnswers(); const rawValue = getAnswerValue(question); const isResidence = question.ui_config?.enable_geoip === true; const storedRegion = isResidence ? getStoredUserGeoRegion() : null; const initial = parseValue(rawValue); const hasSavedAnswer = Boolean( initial.country?.trim() || initial.city?.trim() || (typeof rawValue === "string" && rawValue.trim().length > 0), ); const [mode, setMode] = useState<"auto" | "manual">(() => { if (typeof window !== "undefined") { const stored = localStorage.getItem(`residence_mode_${question.id}`); if (stored === "manual") return "manual"; if (stored === "auto") return "auto"; } return "auto"; }); const isInitialManual = mode === "manual"; const localizedInitialCountry = hasSavedAnswer ? resolveCountryName(initial.country, locale) || initial.country : ""; const initialCity = hasSavedAnswer ? initial.city || "" : ""; const initialLoc = localizedInitialCountry || initialCity ? [localizedInitialCountry, initialCity].filter(Boolean).join(", ") : ""; const [selectedCountry, setSelectedCountry] = useState( () => localizedInitialCountry || "", ); const [cityInput, setCityInput] = useState(() => initialCity); const cityInputStateRef = useRef(initialCity); const selectedCountryStateRef = useRef(localizedInitialCountry || ""); const lastCoordsRef = useRef<{ latitude?: number; longitude?: number } | undefined>( storedRegion?.latitude && storedRegion?.longitude ? { latitude: storedRegion.latitude, longitude: storedRegion.longitude } : undefined, ); useEffect(() => { cityInputStateRef.current = cityInput; }, [cityInput]); useEffect(() => { selectedCountryStateRef.current = selectedCountry; }, [selectedCountry]); const [isOpen, setIsOpen] = useState(false); const [isClosing, setIsClosing] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const cityInputRef = useRef(null); const listRef = useRef(null); const isMountedRef = useRef(true); const isFocusedRef = useRef(false); const debounceTimerRef = useRef(null); const [isDetecting, setIsDetecting] = useState(false); const [detectedLocation, setDetectedLocation] = useState(initialLoc); useEffect(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, []); const closeSheet = useCallback(() => { if (isClosing) return; setIsClosing(true); window.setTimeout(() => { if (isMountedRef.current) { setIsOpen(false); setIsClosing(false); setSearchQuery(""); } }, EXIT_ANIMATION_MS); }, [isClosing]); const openSheet = useCallback(() => { if (disabled) return; setIsOpen(true); setIsClosing(false); }, [disabled]); useSheetScrollLock(isOpen, { onBack: closeSheet }); // Handle escape key useEffect(() => { if (!isOpen) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { closeSheet(); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [isOpen, closeSheet]); const lastInternalAnswerRef = useRef(null); const updateAnswers = useCallback( (country: string, city: string) => { const cleanCountry = country?.trim() || ""; const cleanCity = city?.trim() || ""; const payload = cleanCountry || cleanCity ? { country: cleanCountry, city: cleanCity } : null; lastInternalAnswerRef.current = payload; setAnswerValue(question, payload); }, [question, setAnswerValue], ); const handleAutoClick = async () => { if (typeof window !== "undefined") { localStorage.setItem(`residence_mode_${question.id}`, "auto"); } setMode("auto"); setIsDetecting(true); try { if (isInFlutterWebView()) { const data = await requestAutoLocation(); if (!isMountedRef.current) return; lastCoordsRef.current = { latitude: data.latitude, longitude: data.longitude, }; const rawCountry = data.country || data.country_code || ""; const country = resolveCountryName(rawCountry, locale) || rawCountry || ""; const city = data.city || ""; if (country || city) { setSelectedCountry(country); selectedCountryStateRef.current = country; setCityInput(city); cityInputStateRef.current = city; const loc = [country, city].filter(Boolean).join(", "); setDetectedLocation(loc); updateAnswers(country, city); } } else { const region = await getUserGeoRegion(true); if (!isMountedRef.current) return; const city = region.city || ""; const rawCountry = region.country || region.countryCode || ""; const country = resolveCountryName(rawCountry, locale) || rawCountry || ""; if (region.latitude && region.longitude) { lastCoordsRef.current = { latitude: region.latitude, longitude: region.longitude, }; } if (country || city) { setSelectedCountry(country); selectedCountryStateRef.current = country; setCityInput(city); cityInputStateRef.current = city; const loc = [country, city].filter(Boolean).join(", "); setDetectedLocation(loc); updateAnswers(country, city); } } } catch (err) { console.warn("Auto location error:", err); } finally { if (isMountedRef.current) { setIsDetecting(false); } } }; const handleManualClick = async () => { if (typeof window !== "undefined") { localStorage.setItem(`residence_mode_${question.id}`, "manual"); } setMode("manual"); if (isInFlutterWebView()) { try { const data = await pickManualLocation(lastCoordsRef.current); if (data && isMountedRef.current) { lastCoordsRef.current = { latitude: data.latitude, longitude: data.longitude, }; const rawCountry = data.country || data.country_code || ""; const country = resolveCountryName(rawCountry, locale) || rawCountry || ""; const city = data.city || ""; if (country || city) { setSelectedCountry(country); selectedCountryStateRef.current = country; setCityInput(city); cityInputStateRef.current = city; const loc = [country, city].filter(Boolean).join(", "); setDetectedLocation(loc); updateAnswers(country, city); } } } catch (err) { console.warn("Manual map pick error:", err); } } }; // Synchronize state ONLY if rawValue changes externally (e.g. draft fetch or reset) useEffect(() => { if (isFocusedRef.current) { return; } if (rawValue === lastInternalAnswerRef.current) { return; } if ( typeof rawValue === "object" && rawValue !== null && typeof lastInternalAnswerRef.current === "object" && lastInternalAnswerRef.current !== null ) { const currentObj = lastInternalAnswerRef.current as BirthplaceValue; const rawObj = rawValue as BirthplaceValue; if ( (rawObj.country?.trim() || "") === (currentObj.country?.trim() || "") && (rawObj.city?.trim() || "") === (currentObj.city?.trim() || "") ) { return; } } const updated = parseValue(rawValue); const resolvedC = resolveCountryName(updated.country, locale) || updated.country; if (resolvedC && resolvedC !== selectedCountry) { setSelectedCountry(resolvedC); selectedCountryStateRef.current = resolvedC; } if (updated.city !== cityInput) { setCityInput(updated.city); cityInputStateRef.current = updated.city; } if (resolvedC || updated.city) { setDetectedLocation([resolvedC, updated.city].filter(Boolean).join(", ")); } lastInternalAnswerRef.current = rawValue as BirthplaceValue | string | null; }, [rawValue, locale, selectedCountry, cityInput]); const options = getCountryList(locale); const filteredOptions = options.filter((option) => option.toLowerCase().includes(searchQuery.toLowerCase()), ); const handleSelectCountry = (country: string) => { if (typeof window !== "undefined") { localStorage.setItem(`residence_mode_${question.id}`, "manual"); } setSelectedCountry(country); selectedCountryStateRef.current = country; closeSheet(); updateAnswers(country, cityInputStateRef.current); setDetectedLocation( [country, cityInputStateRef.current].filter(Boolean).join(", "), ); window.setTimeout(() => { cityInputRef.current?.focus({ preventScroll: true }); }, EXIT_ANIMATION_MS); }; const handleCityChange = (e: React.ChangeEvent) => { if (typeof window !== "undefined") { localStorage.setItem(`residence_mode_${question.id}`, "manual"); } const newCity = e.target.value; cityInputStateRef.current = newCity; setCityInput(newCity); setDetectedLocation( [selectedCountryStateRef.current, newCity].filter(Boolean).join(", "), ); if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } debounceTimerRef.current = setTimeout(() => { updateAnswers(selectedCountryStateRef.current, newCity); }, 200); }; const handleCityFocus = () => { isFocusedRef.current = true; }; const handleCityBlur = () => { isFocusedRef.current = false; if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } updateAnswers(selectedCountryStateRef.current, cityInputStateRef.current); }; const isRtl = locale === "fa" || locale === "ar" || locale === "ur"; const selectCountryPlaceholder = isRtl ? "انتخاب کشور" : "Select country"; const cityPlaceholder = question.extras?.placeHolder || (isRtl ? "شهر، منطقه یا محله" : "City, region, or neighborhood"); const searchPlaceholder = locale === "fa" ? "جستجو..." : locale === "ar" ? "بحث..." : locale === "tr" ? "Ara..." : "Search..."; const noResultsText = locale === "fa" ? "موردی یافت نشد" : locale === "ar" ? "لم يتم العثور على نتائج" : "No options found"; return (
{isResidence ? ( <> {/* GeoIP Auto/Manual selector UI */}
{/* Left Side: Location Info */}
{isDetecting ? locale === "fa" ? "در حال شناسایی..." : "Detecting..." : detectedLocation || (locale === "fa" ? "نامشخص" : "Unknown")}
{/* Right Side: Toggle Buttons */}
{/* Auto Button */} {/* Manual Button */}
) : ( <> {/* 1. Country Selection Trigger */}
{/* 2. City Text Input */}
)} {/* Country Selection Bottom Sheet Modal */} {isOpen && createPortal(
event.stopPropagation()} onTouchStart={(event) => event.stopPropagation()} onTouchMove={(event) => event.stopPropagation()} onTouchEnd={(event) => event.stopPropagation()} onClick={(e) => { if (e.target === e.currentTarget) { closeSheet(); } }} >
e.stopPropagation()} > {/* Drag Handle Notch */}
{/* Header with Title and Close Button */}

{selectCountryPlaceholder}

{/* Search Bar */}
setSearchQuery(e.target.value)} placeholder={searchPlaceholder} className="flex-1 bg-transparent text-[14px] font-medium text-[#181818] outline-none placeholder:text-[#98A2B3]" /> {searchQuery ? ( ) : null}
{/* Country Options List */}
{filteredOptions.length > 0 ? ( filteredOptions.map((option) => { const isSelected = selectedCountry === option; return ( ); }) ) : (
{noResultsText}
)}
, document.body, )}
); } export default QuestionBirthplace;