"use client"; import { useEffect, useMemo, useState } from "react"; import type { QuestionField } from "@/lib/schema-adapter"; import { useI18n } from "@/translations/provider"; import { useQuestionAnswers } from "./question-answer-storage"; import QuestionTitle from "./question-title"; import { Input } from "@/components/ui/input"; import { isKnownCountry } from "@/data/countries"; import { resolveDefaultCurrency } from "@/data/currencies"; import { getStoredUserGeoRegion } from "@/lib/geo-region"; import { CurrencySheet } from "./currency-sheet"; type QuestionNumberProps = { question: QuestionField; disabled?: boolean; derivedFromQuestion?: QuestionField; derivedFromQuestionIndex?: number; }; const PERSIAN_DIGITS = ["۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"]; const ARABIC_DIGITS = ["٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"]; export function normalizeNumberString(val: string): string { if (!val) return ""; let result = ""; for (let i = 0; i < val.length; i++) { const char = val[i]; const pIdx = PERSIAN_DIGITS.indexOf(char); if (pIdx !== -1) { result += String(pIdx); continue; } const aIdx = ARABIC_DIGITS.indexOf(char); if (aIdx !== -1) { result += String(aIdx); continue; } if (char === "٫") { result += "."; continue; } result += char; } return result; } const NUMBER_INPUT_PATTERN = /^-?\d*\.?\d*$/; export default function QuestionNumber({ question, disabled, derivedFromQuestion, derivedFromQuestionIndex, }: QuestionNumberProps) { const { dictionary: t, locale } = useI18n(); const { getAnswerValue, setAnswerValue } = useQuestionAnswers(); const value = getAnswerValue(question); const derivedValue = derivedFromQuestion && derivedFromQuestionIndex !== undefined ? getAnswerValue(derivedFromQuestion) : null; useEffect(() => { if (derivedFromQuestion && typeof derivedValue === "string") { const age = calculateAge(derivedValue); if (age !== String(value)) { setAnswerValue(question, age); } } }, [ derivedFromQuestion, derivedValue, question, setAnswerValue, value, ]); useEffect(() => { if (typeof value === "string" && value.length > 0) { const normalized = normalizeNumberString(value); console.log( `[NUM_LOG] useEffect normalize: id=${question.id}, value="${value}", normalized="${normalized}"`, ); if (!NUMBER_INPUT_PATTERN.test(normalized)) { console.log( `[NUM_LOG] useEffect clearing invalid value: id=${question.id}, value="${value}"`, ); setAnswerValue(question, null); } else if (normalized !== value) { const parsed = parseFloat(normalized); console.log( `[NUM_LOG] useEffect updating normalized: id=${question.id}, parsed=${parsed}`, ); setAnswerValue( question, Number.isNaN(parsed) ? normalized : parsed, ); } } }, [question, setAnswerValue, value]); const [min, max] = question.extras.range; const numValue = typeof value === "number" ? value : typeof value === "string" ? parseFloat(normalizeNumberString(value)) : NaN; const isOutOfRange = useMemo(() => { if (Number.isNaN(numValue)) return false; if (min !== 0 && numValue < min) return true; if (max !== 0 && numValue > max) return true; return false; }, [numValue, min, max]); const rawInputValue = value == null ? "" : String(value); const normalizedRaw = normalizeNumberString(rawInputValue); const inputValue = NUMBER_INPUT_PATTERN.test(normalizedRaw) ? normalizedRaw : ""; console.log( `[NUM_LOG] Render: id=${question.id}, value=${JSON.stringify(value)}, inputValue="${inputValue}", isOutOfRange=${isOutOfRange}`, ); const isMonthlyIncome = question.ui_config?.currency_enabled === true; const currencyStorageKey = question.ui_config?.currency_storage_key || "marriage:income:currency"; const countryName = useMemo(() => getCountryFromStorage(), []); const [currencyCode, setCurrencyCode] = useState(() => { if (typeof window !== "undefined") { const stored = window.localStorage.getItem(currencyStorageKey); if (stored) return stored; } const geo = getStoredUserGeoRegion(); return resolveDefaultCurrency({ countryCode: geo?.countryCode, countryName: countryName || geo?.country, fallbackLocale: locale, }); }); const [isCurrencySheetOpen, setIsCurrencySheetOpen] = useState(false); useEffect(() => { if (typeof window !== "undefined") { const stored = window.localStorage.getItem(currencyStorageKey); if (stored) { setCurrencyCode(stored); return; } } const geo = getStoredUserGeoRegion(); const derived = resolveDefaultCurrency({ countryCode: geo?.countryCode, countryName: countryName || geo?.country, fallbackLocale: locale, }); setCurrencyCode(derived); }, [countryName, currencyStorageKey, locale]); const placeholderCurrency = useMemo(() => { if (currencyCode === "TOMAN") { return locale === "fa" || locale === "fa-ir" ? "تومان" : "TOMAN"; } return currencyCode; }, [currencyCode, locale]); const dynamicPlaceholder = useMemo(() => { if (!isMonthlyIncome) { return question.extras.placeHolder; } return locale === "fa" || locale === "fa-ir" ? `مثال: ۴۰۰۰ ${placeholderCurrency}` : `e.g. 4000 ${placeholderCurrency}`; }, [ isMonthlyIncome, question.extras.placeHolder, placeholderCurrency, locale, ]); const [localTextValue, setLocalTextValue] = useState(() => formatNumberWithCommas(inputValue), ); useEffect(() => { const formatted = formatNumberWithCommas( value == null ? "" : String(value), ); const cleanLocal = localTextValue.replace(/,/g, ""); const cleanFormatted = formatted.replace(/,/g, ""); if (cleanFormatted !== cleanLocal) { setLocalTextValue(formatted); } }, [value, localTextValue]); if (isMonthlyIncome) { return (
{ const nextValue = event.target.value; const normalized = normalizeNumberString(nextValue); const cleanValue = normalized.replace(/,/g, ""); if ( cleanValue !== "" && cleanValue !== "-" && !NUMBER_INPUT_PATTERN.test(cleanValue) ) { return; } const formatted = formatNumberWithCommas(cleanValue); const finalFormatted = normalized.endsWith(".") ? `${formatted}.` : formatted; setLocalTextValue(finalFormatted); if (cleanValue === "" || cleanValue === "-") { setAnswerValue(question, null); } else { const parsed = parseFloat(cleanValue); setAnswerValue( question, Number.isNaN(parsed) ? cleanValue : parsed, ); } }} className={[ "h-[54px] w-full rounded-[16px] border px-4.5 text-[15px] font-medium text-[#181818] outline-none transition-all placeholder:text-[#98A2B3] focus:border-[#6F6F6F] focus:ring-1 focus:ring-[#6F6F6F] disabled:bg-[#F5F2F1] disabled:text-[#7C7472]", isOutOfRange ? "border-[#F2465F] ring-1 ring-[#F2465F]" : "border-[#D0D5DD] hover:border-[#98A2B3] bg-white", ].join(" ")} />
setIsCurrencySheetOpen(false)} selectedCurrency={currencyCode} onSelectCurrency={(code) => { setCurrencyCode(code); if (typeof window !== "undefined") { window.localStorage.setItem(currencyStorageKey, code); } }} /> {isOutOfRange ? ( {t[ "The value entered seems incorrect. Please provide a realistic value." ] || `Please enter a value between ${min} and ${max}`} ) : null}
); } return (
{ const raw = event.target.value; const normalized = normalizeNumberString(raw); const cleaned = normalized.replace(/[^0-9.-]/g, ""); console.log( `[NUM_LOG] onChange: id=${question.id}, raw="${raw}", normalized="${normalized}", cleaned="${cleaned}"`, ); if (cleaned === "" || cleaned === "-") { console.log(`[NUM_LOG] onChange clearing value (empty)`); setAnswerValue(question, null); return; } if (!NUMBER_INPUT_PATTERN.test(cleaned)) { console.log(`[NUM_LOG] onChange rejected pattern: "${cleaned}"`); return; } const parsed = parseFloat(cleaned); console.log( `[NUM_LOG] onChange calling setAnswerValue with:`, Number.isNaN(parsed) ? cleaned : parsed, ); setAnswerValue( question, Number.isNaN(parsed) ? cleaned : parsed, ); }} className={[ "h-[54px] w-full rounded-[16px] border px-4.5 text-[15px] font-medium text-[#181818] outline-none transition-all placeholder:text-[#98A2B3] focus:border-[#6F6F6F] focus:ring-1 focus:ring-[#6F6F6F] disabled:bg-[#F5F2F1] disabled:text-[#7C7472]", isOutOfRange ? "border-[#F2465F] ring-1 ring-[#F2465F]" : "border-[#D0D5DD] hover:border-[#98A2B3] bg-white", ].join(" ")} /> {isOutOfRange ? ( {t[ "The value entered seems incorrect. Please provide a realistic value." ] || `Please enter a value between ${min} and ${max}`} ) : null}
); } function calculateAge(dateOfBirth: string) { const birthDate = new Date(dateOfBirth); if (Number.isNaN(birthDate.getTime())) { return ""; } const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); const monthDifference = today.getMonth() - birthDate.getMonth(); if ( monthDifference < 0 || (monthDifference === 0 && today.getDate() < birthDate.getDate()) ) { age -= 1; } return String(Math.max(age, 0)); } function getCountryFromStorage(): string { if (typeof window === "undefined") return ""; try { const rawValue = window.localStorage.getItem( "marriage:sections:contact_residence_family_communication:answers", ); if (!rawValue) return ""; const storedValue = JSON.parse(rawValue); const field = storedValue.fields?.find( (f: { type?: string; key?: string; value?: unknown }) => f.type === "birthplace" || f.key?.includes("current_residence") || f.key?.includes("mhl_skwnt_fly"), ); const value = field?.value; if (typeof value === "object" && value !== null) { const obj = value as { country?: string; city?: string }; if (typeof obj.country === "string" && obj.country.trim()) { return obj.country.trim(); } } if (typeof value === "string") { const parts = value.split(",").map((p) => p.trim()); if (parts.length >= 2) { if (isKnownCountry(parts[1])) return parts[1]; if (isKnownCountry(parts[0])) return parts[0]; return parts[1]; } return parts[0] || ""; } } catch { // Ignore } return ""; } function formatNumberWithCommas(val: string): string { if (!val) return ""; const parts = val.split("."); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); return parts.join("."); }