|
|
|
@ -19,6 +19,11 @@ import { |
|
|
|
getStoredUserGeoRegion, |
|
|
|
subscribeToUserGeoRegion, |
|
|
|
} from "@/lib/geo-region"; |
|
|
|
import { |
|
|
|
isInFlutterWebView, |
|
|
|
requestAutoLocation, |
|
|
|
pickManualLocation, |
|
|
|
} from "@/lib/webview-actions"; |
|
|
|
|
|
|
|
const EXIT_ANIMATION_MS = 220; |
|
|
|
|
|
|
|
@ -171,23 +176,11 @@ export function QuestionBirthplace({ |
|
|
|
|
|
|
|
const isInitialManual = mode === "manual"; |
|
|
|
|
|
|
|
const defaultCountryFallback = |
|
|
|
isResidence && locale === "fa" |
|
|
|
? resolveCountryName("IR", "fa") || "ایران" |
|
|
|
const localizedInitialCountry = hasSavedAnswer |
|
|
|
? resolveCountryName(initial.country, locale) || initial.country |
|
|
|
: ""; |
|
|
|
|
|
|
|
const localizedInitialCountry = |
|
|
|
resolveCountryName(initial.country, locale) || |
|
|
|
initial.country || |
|
|
|
(!hasSavedAnswer && !isInitialManual && storedRegion?.country |
|
|
|
? resolveCountryName(storedRegion.country, locale) || storedRegion.country |
|
|
|
: defaultCountryFallback); |
|
|
|
|
|
|
|
const initialCity = |
|
|
|
initial.city || |
|
|
|
(!hasSavedAnswer && !isInitialManual && storedRegion?.city |
|
|
|
? storedRegion.city |
|
|
|
: ""); |
|
|
|
const initialCity = hasSavedAnswer ? initial.city || "" : ""; |
|
|
|
|
|
|
|
const initialLoc = |
|
|
|
localizedInitialCountry || initialCity |
|
|
|
@ -202,6 +195,12 @@ export function QuestionBirthplace({ |
|
|
|
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]); |
|
|
|
@ -221,7 +220,6 @@ export function QuestionBirthplace({ |
|
|
|
|
|
|
|
const [isDetecting, setIsDetecting] = useState(false); |
|
|
|
const [detectedLocation, setDetectedLocation] = useState(initialLoc); |
|
|
|
const hasAutoDetectedRef = useRef(false); |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
isMountedRef.current = true; |
|
|
|
@ -283,25 +281,25 @@ export function QuestionBirthplace({ |
|
|
|
[question, setAnswerValue], |
|
|
|
); |
|
|
|
|
|
|
|
// Subscribe to live geo region updates (e.g. when Flutter bridge responds asynchronously)
|
|
|
|
useEffect(() => { |
|
|
|
if (!isResidence) return; |
|
|
|
const handleAutoClick = async () => { |
|
|
|
if (typeof window !== "undefined") { |
|
|
|
localStorage.setItem(`residence_mode_${question.id}`, "auto"); |
|
|
|
} |
|
|
|
setMode("auto"); |
|
|
|
setIsDetecting(true); |
|
|
|
|
|
|
|
const unsubscribe = subscribeToUserGeoRegion((region) => { |
|
|
|
try { |
|
|
|
if (isInFlutterWebView()) { |
|
|
|
const data = await requestAutoLocation(); |
|
|
|
if (!isMountedRef.current) return; |
|
|
|
// If user has already switched to manual mode, do not overwrite manual edits
|
|
|
|
const currentStoredMode = |
|
|
|
typeof window !== "undefined" |
|
|
|
? localStorage.getItem(`residence_mode_${question.id}`) |
|
|
|
: null; |
|
|
|
if (currentStoredMode === "manual" || mode === "manual") return; |
|
|
|
|
|
|
|
const rawCountry = region.country || region.countryCode || ""; |
|
|
|
lastCoordsRef.current = { |
|
|
|
latitude: data.latitude, |
|
|
|
longitude: data.longitude, |
|
|
|
}; |
|
|
|
const rawCountry = data.country || data.country_code || ""; |
|
|
|
const country = |
|
|
|
resolveCountryName(rawCountry, locale) || |
|
|
|
rawCountry || |
|
|
|
defaultCountryFallback; |
|
|
|
const city = region.city || ""; |
|
|
|
resolveCountryName(rawCountry, locale) || rawCountry || ""; |
|
|
|
const city = data.city || ""; |
|
|
|
|
|
|
|
if (country || city) { |
|
|
|
setSelectedCountry(country); |
|
|
|
@ -311,68 +309,21 @@ export function QuestionBirthplace({ |
|
|
|
const loc = [country, city].filter(Boolean).join(", "); |
|
|
|
setDetectedLocation(loc); |
|
|
|
updateAnswers(country, city); |
|
|
|
setIsDetecting(false); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
return () => { |
|
|
|
unsubscribe(); |
|
|
|
}; |
|
|
|
}, [ |
|
|
|
isResidence, |
|
|
|
mode, |
|
|
|
locale, |
|
|
|
question.id, |
|
|
|
defaultCountryFallback, |
|
|
|
updateAnswers, |
|
|
|
]); |
|
|
|
|
|
|
|
// 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); |
|
|
|
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}`) |
|
|
|
: null; |
|
|
|
|
|
|
|
if (storedMode === "manual") { |
|
|
|
setMode("manual"); |
|
|
|
} else { |
|
|
|
setMode("auto"); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
setIsDetecting(true); |
|
|
|
|
|
|
|
try { |
|
|
|
const region = await getUserGeoRegion(force); |
|
|
|
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 || |
|
|
|
defaultCountryFallback; |
|
|
|
resolveCountryName(rawCountry, locale) || rawCountry || ""; |
|
|
|
|
|
|
|
if (region.latitude && region.longitude) { |
|
|
|
lastCoordsRef.current = { |
|
|
|
latitude: region.latitude, |
|
|
|
longitude: region.longitude, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
if (country || city) { |
|
|
|
setSelectedCountry(country); |
|
|
|
@ -383,83 +334,49 @@ export function QuestionBirthplace({ |
|
|
|
setDetectedLocation(loc); |
|
|
|
updateAnswers(country, city); |
|
|
|
} |
|
|
|
} catch { |
|
|
|
// Keep in auto mode on error, do not force manual
|
|
|
|
} |
|
|
|
} catch (err) { |
|
|
|
console.warn("Auto location error:", err); |
|
|
|
} finally { |
|
|
|
if (isMountedRef.current) { |
|
|
|
setIsDetecting(false); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
[rawValue, locale, question.id, defaultCountryFallback, updateAnswers], |
|
|
|
); |
|
|
|
|
|
|
|
// Auto-detect and pre-fill on initial mount
|
|
|
|
useEffect(() => { |
|
|
|
if (isLoading) return; |
|
|
|
if (isResidence && !hasAutoDetectedRef.current) { |
|
|
|
hasAutoDetectedRef.current = true; |
|
|
|
const storedMode = |
|
|
|
typeof window !== "undefined" |
|
|
|
? localStorage.getItem(`residence_mode_${question.id}`) |
|
|
|
: null; |
|
|
|
|
|
|
|
if (storedMode === "manual") { |
|
|
|
setMode("manual"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Pre-fill answer immediately if initial values exist and no answer recorded yet
|
|
|
|
if (!hasSavedAnswer && (localizedInitialCountry || initialCity)) { |
|
|
|
updateAnswers(localizedInitialCountry, initialCity); |
|
|
|
} |
|
|
|
|
|
|
|
const parsed = parseValue(rawValue); |
|
|
|
if (!parsed.country && !parsed.city) { |
|
|
|
void detectLocation(false); |
|
|
|
} else { |
|
|
|
void detectLocation(false); |
|
|
|
} |
|
|
|
} |
|
|
|
}, [ |
|
|
|
isResidence, |
|
|
|
isLoading, |
|
|
|
detectLocation, |
|
|
|
rawValue, |
|
|
|
question.id, |
|
|
|
hasSavedAnswer, |
|
|
|
localizedInitialCountry, |
|
|
|
initialCity, |
|
|
|
updateAnswers, |
|
|
|
]); |
|
|
|
|
|
|
|
const handleAutoClick = () => { |
|
|
|
if (typeof window !== "undefined") { |
|
|
|
localStorage.setItem(`residence_mode_${question.id}`, "auto"); |
|
|
|
} |
|
|
|
setMode("auto"); |
|
|
|
detectLocation(true); |
|
|
|
}; |
|
|
|
|
|
|
|
const handleManualClick = () => { |
|
|
|
const handleManualClick = async () => { |
|
|
|
if (typeof window !== "undefined") { |
|
|
|
localStorage.setItem(`residence_mode_${question.id}`, "manual"); |
|
|
|
} |
|
|
|
setMode("manual"); |
|
|
|
const parsed = parseValue(rawValue); |
|
|
|
const resolvedC = |
|
|
|
resolveCountryName(selectedCountry || parsed.country, locale) || |
|
|
|
selectedCountry || |
|
|
|
parsed.country || |
|
|
|
defaultCountryFallback; |
|
|
|
const country = resolvedC; |
|
|
|
const city = cityInput !== "" ? cityInput : parsed.city; |
|
|
|
|
|
|
|
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); |
|
|
|
setDetectedLocation([country, city].filter(Boolean).join(", ")); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (err) { |
|
|
|
console.warn("Manual map pick error:", err); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// Synchronize state ONLY if rawValue changes externally (e.g. draft fetch or reset)
|
|
|
|
|