@ -2,7 +2,7 @@
import { useCallback , useEffect , useRef , useState } from "react" ;
import { useCallback , useEffect , useRef , useState } from "react" ;
import { createPortal } from "react-dom" ;
import { createPortal } from "react-dom" ;
import { getCountryList , resolveCountryName , isKnownCountry , COUNTRIES_EN , COUNTRIES_FA } from "@/data/countries" ;
import { getCountryList , resolveCountryName , isKnownCountry } from "@/data/countries" ;
import type { QuestionField } from "@/lib/schema-adapter" ;
import type { QuestionField } from "@/lib/schema-adapter" ;
import { useI18n } from "@/translations/provider" ;
import { useI18n } from "@/translations/provider" ;
import { useQuestionAnswers } from "./question-answer-storage" ;
import { useQuestionAnswers } from "./question-answer-storage" ;
@ -137,12 +137,34 @@ export function QuestionBirthplace({
const storedRegion = isResidence ? getStoredUserGeoRegion ( ) : null ;
const storedRegion = isResidence ? getStoredUserGeoRegion ( ) : null ;
const initial = parseValue ( rawValue ) ;
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" || stored === "auto" ) return stored ;
const legacyChecked = localStorage . getItem ( "hasCheckedGeoIPResidence" ) ;
if ( legacyChecked === "true" ) return "manual" ;
}
return "auto" ;
} ) ;
const isInitialManual = mode === "manual" ;
const localizedInitialCountry =
const localizedInitialCountry =
resolveCountryName ( initial . country || storedRegion ? . country , locale ) ||
resolveCountryName ( initial . country , locale ) ||
initial . country ||
initial . country ||
storedRegion ? . country ||
"" ;
const initialCity = initial . city || storedRegion ? . city || "" ;
( ! hasSavedAnswer && ! isInitialManual && storedRegion ? . country
? resolveCountryName ( storedRegion . country , locale ) || storedRegion . country
: "" ) ;
const initialCity =
initial . city || ( ! hasSavedAnswer && ! isInitialManual && storedRegion ? . city ? storedRegion . city : "" ) ;
const initialLoc =
const initialLoc =
localizedInitialCountry || initialCity
localizedInitialCountry || initialCity
? [ localizedInitialCountry , initialCity ] . filter ( Boolean ) . join ( ", " )
? [ localizedInitialCountry , initialCity ] . filter ( Boolean ) . join ( ", " )
@ -155,6 +177,17 @@ export function QuestionBirthplace({
( ) = > initialCity ,
( ) = > initialCity ,
) ;
) ;
const cityInputStateRef = useRef ( initialCity ) ;
const selectedCountryStateRef = useRef ( localizedInitialCountry || "" ) ;
useEffect ( ( ) = > {
cityInputStateRef . current = cityInput ;
} , [ cityInput ] ) ;
useEffect ( ( ) = > {
selectedCountryStateRef . current = selectedCountry ;
} , [ selectedCountry ] ) ;
const [ isOpen , setIsOpen ] = useState ( false ) ;
const [ isOpen , setIsOpen ] = useState ( false ) ;
const [ isClosing , setIsClosing ] = useState ( false ) ;
const [ isClosing , setIsClosing ] = useState ( false ) ;
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
@ -164,7 +197,6 @@ export function QuestionBirthplace({
const isFocusedRef = useRef ( false ) ;
const isFocusedRef = useRef ( false ) ;
const debounceTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
const debounceTimerRef = useRef < NodeJS.Timeout | null > ( null ) ;
const [ mode , setMode ] = useState < "auto" | "manual" > ( "auto" ) ;
const [ isDetecting , setIsDetecting ] = useState ( false ) ;
const [ isDetecting , setIsDetecting ] = useState ( false ) ;
const [ detectedLocation , setDetectedLocation ] = useState ( initialLoc ) ;
const [ detectedLocation , setDetectedLocation ] = useState ( initialLoc ) ;
const hasAutoDetectedRef = useRef ( false ) ;
const hasAutoDetectedRef = useRef ( false ) ;
@ -227,67 +259,120 @@ export function QuestionBirthplace({
} ;
} ;
// GeoIP detection logic using unified getUserGeoRegion
// GeoIP detection logic using unified getUserGeoRegion
const detectLocation = useCallback ( async ( force = false ) = > {
// If there is already a saved answer and we are not forcing, display it
if ( rawValue && ! force ) {
const parsed = parseValue ( rawValue ) ;
const cName = resolveCountryName ( parsed . country , locale ) || parsed . country ;
if ( cName || parsed . city ) {
const loc = [ cName , parsed . city ] . filter ( Boolean ) . join ( ", " ) ;
if ( cName ) setSelectedCountry ( cName ) ;
if ( parsed . city ) setCityInput ( parsed . city ) ;
setDetectedLocation ( loc ) ;
setMode ( "auto" ) ;
return ;
const detectLocation = useCallback (
async ( force = false ) = > {
// If there is already a saved answer and we are not forcing, display it
if ( rawValue && ! force ) {
const parsed = parseValue ( rawValue ) ;
const cName =
resolveCountryName ( parsed . country , locale ) || parsed . country ;
if ( cName || parsed . city ) {
const loc = [ cName , parsed . city ] . filter ( Boolean ) . join ( ", " ) ;
if ( cName ) {
setSelectedCountry ( cName ) ;
selectedCountryStateRef . current = cName ;
}
if ( parsed . city ) {
setCityInput ( parsed . city ) ;
cityInputStateRef . current = parsed . city ;
}
setDetectedLocation ( loc ) ;
const storedMode =
typeof window !== "undefined"
? localStorage . getItem ( ` residence_mode_ ${ question . id } ` ) ||
( localStorage . getItem ( "hasCheckedGeoIPResidence" ) === "true"
? "manual"
: null )
: null ;
if ( storedMode === "manual" ) {
setMode ( "manual" ) ;
} else if ( storedMode === "auto" ) {
setMode ( "auto" ) ;
}
return ;
}
}
}
}
setIsDetecting ( true ) ;
try {
const region = await getUserGeoRegion ( force ) ;
if ( ! isMountedRef . current ) return ;
const city = region . city || "" ;
const rawCountry = region . country || region . countryCode || "" ;
const country = resolveCountryName ( rawCountry , locale ) || rawCountry ;
if ( country || city ) {
setSelectedCountry ( country ) ;
setCityInput ( city ) ;
const loc = [ country , city ] . filter ( Boolean ) . join ( ", " ) ;
setDetectedLocation ( loc ) ;
updateAnswers ( country , city ) ;
setMode ( "auto" ) ;
} else {
setMode ( "manual" ) ;
}
} catch {
if ( isMountedRef . current ) {
setMode ( "manual" ) ;
setIsDetecting ( true ) ;
try {
const region = await getUserGeoRegion ( force ) ;
if ( ! isMountedRef . current ) return ;
const city = region . city || "" ;
const rawCountry = region . country || region . countryCode || "" ;
const country = resolveCountryName ( rawCountry , locale ) || rawCountry ;
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 ) ;
setMode ( "auto" ) ;
if ( typeof window !== "undefined" ) {
localStorage . setItem ( ` residence_mode_ ${ question . id } ` , "auto" ) ;
}
} else {
setMode ( "manual" ) ;
if ( typeof window !== "undefined" ) {
localStorage . setItem ( ` residence_mode_ ${ question . id } ` , "manual" ) ;
}
}
} catch {
if ( isMountedRef . current ) {
setMode ( "manual" ) ;
}
} finally {
if ( isMountedRef . current ) {
setIsDetecting ( false ) ;
}
}
}
} finally {
if ( isMountedRef . current ) {
setIsDetecting ( false ) ;
}
}
} , [ rawValue , locale , question , setAnswerValue ] ) ;
} ,
[ rawValue , locale , question , setAnswerValue ] ,
) ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
if ( isLoading ) return ;
if ( isLoading ) return ;
if ( isResidence && ! hasAutoDetectedRef . current ) {
if ( isResidence && ! hasAutoDetectedRef . current ) {
hasAutoDetectedRef . current = true ;
hasAutoDetectedRef . current = true ;
void detectLocation ( ) ;
const storedMode =
typeof window !== "undefined"
? localStorage . getItem ( ` residence_mode_ ${ question . id } ` ) ||
( localStorage . getItem ( "hasCheckedGeoIPResidence" ) === "true"
? "manual"
: null )
: null ;
if ( storedMode === "manual" ) {
setMode ( "manual" ) ;
return ;
}
const parsed = parseValue ( rawValue ) ;
if ( ! parsed . country && ! parsed . city ) {
void detectLocation ( ) ;
} else {
void detectLocation ( false ) ;
}
}
}
} , [ isResidence , isLoading , detectLocation ] ) ;
} , [ isResidence , isLoading , detectLocation , rawValue , question . id ] ) ;
const handleAutoClick = ( ) = > {
const handleAutoClick = ( ) = > {
if ( typeof window !== "undefined" ) {
localStorage . setItem ( ` residence_mode_ ${ question . id } ` , "auto" ) ;
}
setMode ( "auto" ) ;
setMode ( "auto" ) ;
detectLocation ( true ) ;
detectLocation ( true ) ;
} ;
} ;
const handleManualClick = ( ) = > {
const handleManualClick = ( ) = > {
if ( typeof window !== "undefined" ) {
if ( typeof window !== "undefined" ) {
localStorage . setItem ( ` residence_mode_ ${ question . id } ` , "manual" ) ;
localStorage . setItem ( "hasCheckedGeoIPResidence" , "true" ) ;
localStorage . setItem ( "hasCheckedGeoIPResidence" , "true" ) ;
}
}
setMode ( "manual" ) ;
setMode ( "manual" ) ;
@ -297,9 +382,11 @@ export function QuestionBirthplace({
selectedCountry ||
selectedCountry ||
parsed . country ;
parsed . country ;
const country = resolvedC ;
const country = resolvedC ;
const city = cityInput || parsed . city ;
const city = cityInput !== "" ? cityInput : parsed. city;
setSelectedCountry ( country ) ;
setSelectedCountry ( country ) ;
selectedCountryStateRef . current = country ;
setCityInput ( city ) ;
setCityInput ( city ) ;
cityInputStateRef . current = city ;
updateAnswers ( country , city ) ;
updateAnswers ( country , city ) ;
setDetectedLocation ( [ country , city ] . filter ( Boolean ) . join ( ", " ) ) ;
setDetectedLocation ( [ country , city ] . filter ( Boolean ) . join ( ", " ) ) ;
} ;
} ;
@ -331,9 +418,11 @@ export function QuestionBirthplace({
const resolvedC = resolveCountryName ( updated . country , locale ) || updated . country ;
const resolvedC = resolveCountryName ( updated . country , locale ) || updated . country ;
if ( resolvedC !== selectedCountry ) {
if ( resolvedC !== selectedCountry ) {
setSelectedCountry ( resolvedC ) ;
setSelectedCountry ( resolvedC ) ;
selectedCountryStateRef . current = resolvedC ;
}
}
if ( updated . city !== cityInput ) {
if ( updated . city !== cityInput ) {
setCityInput ( updated . city ) ;
setCityInput ( updated . city ) ;
cityInputStateRef . current = updated . city ;
}
}
if ( resolvedC || updated . city ) {
if ( resolvedC || updated . city ) {
setDetectedLocation ( [ resolvedC , updated . city ] . filter ( Boolean ) . join ( ", " ) ) ;
setDetectedLocation ( [ resolvedC , updated . city ] . filter ( Boolean ) . join ( ", " ) ) ;
@ -348,12 +437,14 @@ export function QuestionBirthplace({
const handleSelectCountry = ( country : string ) = > {
const handleSelectCountry = ( country : string ) = > {
if ( typeof window !== "undefined" ) {
if ( typeof window !== "undefined" ) {
localStorage . setItem ( ` residence_mode_ ${ question . id } ` , "manual" ) ;
localStorage . setItem ( "hasCheckedGeoIPResidence" , "true" ) ;
localStorage . setItem ( "hasCheckedGeoIPResidence" , "true" ) ;
}
}
setSelectedCountry ( country ) ;
setSelectedCountry ( country ) ;
selectedCountryStateRef . current = country ;
closeSheet ( ) ;
closeSheet ( ) ;
updateAnswers ( country , cityInput ) ;
setDetectedLocation ( [ country , cityInput ] . filter ( Boolean ) . join ( ", " ) ) ;
updateAnswers ( country , cityInputStateRef . current ) ;
setDetectedLocation ( [ country , cityInputStateRef . current ] . filter ( Boolean ) . join ( ", " ) ) ;
window . setTimeout ( ( ) = > {
window . setTimeout ( ( ) = > {
cityInputRef . current ? . focus ( { preventScroll : true } ) ;
cityInputRef . current ? . focus ( { preventScroll : true } ) ;
} , EXIT_ANIMATION_MS ) ;
} , EXIT_ANIMATION_MS ) ;
@ -361,18 +452,20 @@ export function QuestionBirthplace({
const handleCityChange = ( e : React.ChangeEvent < HTMLInputElement > ) = > {
const handleCityChange = ( e : React.ChangeEvent < HTMLInputElement > ) = > {
if ( typeof window !== "undefined" ) {
if ( typeof window !== "undefined" ) {
localStorage . setItem ( ` residence_mode_ ${ question . id } ` , "manual" ) ;
localStorage . setItem ( "hasCheckedGeoIPResidence" , "true" ) ;
localStorage . setItem ( "hasCheckedGeoIPResidence" , "true" ) ;
}
}
const newCity = e . target . value ;
const newCity = e . target . value ;
cityInputStateRef . current = newCity ;
setCityInput ( newCity ) ;
setCityInput ( newCity ) ;
setDetectedLocation ( [ selectedCountry , newCity ] . filter ( Boolean ) . join ( ", " ) ) ;
setDetectedLocation ( [ selectedCountryStateRef . current , newCity ] . filter ( Boolean ) . join ( ", " ) ) ;
if ( debounceTimerRef . current ) {
if ( debounceTimerRef . current ) {
clearTimeout ( debounceTimerRef . current ) ;
clearTimeout ( debounceTimerRef . current ) ;
}
}
debounceTimerRef . current = setTimeout ( ( ) = > {
debounceTimerRef . current = setTimeout ( ( ) = > {
updateAnswers ( selectedCountry , newCity ) ;
} , 25 0 ) ;
updateAnswers ( selectedCountryStateRef . current , newCity ) ;
} , 20 0 ) ;
} ;
} ;
const handleCityFocus = ( ) = > {
const handleCityFocus = ( ) = > {
@ -384,7 +477,7 @@ export function QuestionBirthplace({
if ( debounceTimerRef . current ) {
if ( debounceTimerRef . current ) {
clearTimeout ( debounceTimerRef . current ) ;
clearTimeout ( debounceTimerRef . current ) ;
}
}
updateAnswers ( selectedCountry , cityInput ) ;
updateAnswers ( selectedCountryStateRef . current , cityInputStateRef . curren t ) ;
} ;
} ;
const isRtl = locale === "fa" || locale === "ar" || locale === "ur" ;
const isRtl = locale === "fa" || locale === "ar" || locale === "ur" ;
@ -580,6 +673,8 @@ export function QuestionBirthplace({
disabled = { disabled }
disabled = { disabled }
value = { cityInput }
value = { cityInput }
onChange = { handleCityChange }
onChange = { handleCityChange }
onFocus = { handleCityFocus }
onBlur = { handleCityBlur }
placeholder = { cityPlaceholder }
placeholder = { cityPlaceholder }
/ >
/ >
< / div >
< / div >