@ -1,6 +1,10 @@
"use client" ;
import { PhoneNumberFormat , PhoneNumberUtil } from "google-libphonenumber" ;
import {
AsYouTypeFormatter ,
PhoneNumberFormat ,
PhoneNumberUtil ,
} from "google-libphonenumber" ;
import { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
import { createPortal } from "react-dom" ;
import type { MarriagePhoneFieldValue } from "@/hooks/marriage/types" ;
@ -264,10 +268,11 @@ function readPhoneValue(value: unknown, fallbackCode: string): PhoneValueParts {
function getMaxLengthForCountry ( codeValue : string ) : number {
try {
const cleanCode = codeValue . replace ( /[^\d]/g , "" ) ;
if ( ! cleanCode ) return 15 ;
if ( ! cleanCode ) return 18 ;
if ( cleanCode === "98" ) return 12 ; // 3 + 1 + 3 + 1 + 4 = 12 chars
const countryCode = Number ( cleanCode ) ;
const regionCode = phoneUtil . getRegionCodeForCountryCode ( countryCode ) ;
if ( ! regionCode || regionCode === "ZZ" ) return 15 ;
if ( ! regionCode || regionCode === "ZZ" ) return 18 ;
const exampleMobile = phoneUtil . getExampleNumberForType (
regionCode ,
@ -283,9 +288,93 @@ function getMaxLengthForCountry(codeValue: string): number {
: 0 ;
const baseLen = Math . max ( lenMobile , lenGeneral , 8 ) ;
return baseLen + 1 ;
return baseLen + 6 ;
} catch {
return 15 ;
return 18 ;
}
}
function getCountryPlaceholder (
codeValue : string ,
defaultPlaceholder? : string ,
) : string {
try {
const cleanCode = codeValue . replace ( /[^\d]/g , "" ) ;
if ( ! cleanCode ) {
return defaultPlaceholder ? . replace ( /^\+\d+\s*/ , "" ) || "912 345 6789" ;
}
if ( cleanCode === "98" ) {
return "912 345 6789" ;
}
const countryCode = Number ( cleanCode ) ;
const regionCode = phoneUtil . getRegionCodeForCountryCode ( countryCode ) ;
if ( ! regionCode || regionCode === "ZZ" ) {
return defaultPlaceholder ? . replace ( /^\+\d+\s*/ , "" ) || "912 345 6789" ;
}
const example =
phoneUtil . getExampleNumberForType (
regionCode ,
1 , // MOBILE
) || phoneUtil . getExampleNumber ( regionCode ) ;
if ( ! example ) {
return defaultPlaceholder ? . replace ( /^\+\d+\s*/ , "" ) || "912 345 6789" ;
}
const intl = phoneUtil . format ( example , PhoneNumberFormat . INTERNATIONAL ) ;
const prefix = ` + ${ cleanCode } ` ;
if ( intl . startsWith ( prefix ) ) {
return intl . slice ( prefix . length ) ;
}
return String ( example . getNationalNumber ( ) ) ;
} catch {
return defaultPlaceholder ? . replace ( /^\+\d+\s*/ , "" ) || "912 345 6789" ;
}
}
function formatPhoneNumberAsYouType (
rawInput : string ,
codeValue : string ,
) : string {
const cleanCode = codeValue . replace ( /[^\d]/g , "" ) ;
const rawDigits = rawInput . replace ( /\D/g , "" ) ;
if ( ! rawDigits ) return "" ;
// Special telegram-style handling for Iran (+98): 3 - 3 - 4 (e.g. 912 345 6789)
if ( cleanCode === "98" ) {
const digits = rawDigits . startsWith ( "0" ) ? rawDigits . slice ( 1 ) : rawDigits ;
const max10 = digits . slice ( 0 , 10 ) ;
if ( max10 . length <= 3 ) return max10 ;
if ( max10 . length <= 6 ) return ` ${ max10 . slice ( 0 , 3 ) } ${ max10 . slice ( 3 ) } ` ;
return ` ${ max10 . slice ( 0 , 3 ) } ${ max10 . slice ( 3 , 6 ) } ${ max10 . slice ( 6 ) } ` ;
}
const countryCode = Number ( cleanCode ) ;
const regionCode = phoneUtil . getRegionCodeForCountryCode ( countryCode ) ;
if ( ! regionCode || regionCode === "ZZ" ) {
return rawDigits ;
}
try {
const formatter = new AsYouTypeFormatter ( regionCode ) ;
let formatted = "" ;
const fullNumber = ` + ${ cleanCode } ${ rawDigits } ` ;
for ( const char of fullNumber ) {
formatted = formatter . inputDigit ( char ) ;
}
const codePrefix = ` + ${ cleanCode } ` ;
if ( formatted . startsWith ( codePrefix ) ) {
return formatted . slice ( codePrefix . length ) ;
}
if ( formatted . startsWith ( ` + ${ cleanCode } ` ) ) {
return formatted . slice ( ` + ${ cleanCode } ` . length ) . trim ( ) ;
}
return rawDigits ;
} catch {
return rawDigits ;
}
}
@ -433,13 +522,23 @@ export function QuestionPhone({
} , [ value , defaultCodeValue ] ) ;
const initialPhone = useMemo ( ( ) = > {
return readPhoneValue ( value , defaultCodeValue ) . phoneValue ;
} , [ value , defaultCodeValue ] ) ;
const rawPhone = readPhoneValue ( value , defaultCodeValue ) . phoneValue ;
return formatPhoneNumberAsYouType ( rawPhone , initialCode ) ;
} , [ value , defaultCodeValue , initialCode ] ) ;
const [ codeValue , setCodeValue ] = useState ( initialCode ) ;
const [ phoneValue , setPhoneValue ] = useState ( initialPhone ) ;
const [ isFocused , setIsFocused ] = useState ( false ) ;
const [ isTouched , setIsTouched ] = useState ( false ) ;
const lastCommittedValueRef = useRef ( value ) ;
const dynamicPlaceholder = useMemo ( ( ) = > {
return getCountryPlaceholder (
codeValue || defaultCodeValue ,
question . extras ? . placeHolder ,
) ;
} , [ codeValue , defaultCodeValue , question . extras ? . placeHolder ] ) ;
const [ isOpen , setIsOpen ] = useState ( false ) ;
const [ isClosing , setIsClosing ] = useState ( false ) ;
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
@ -471,6 +570,11 @@ export function QuestionPhone({
const onGeoCodeResolved = ( resolvedCode : string ) = > {
if ( ! isMounted || userInteractedRef . current ) return ;
setCodeValue ( resolvedCode ) ;
if ( phoneValue ) {
setPhoneValue ( ( prev ) = >
formatPhoneNumberAsYouType ( prev , resolvedCode ) ,
) ;
}
setIsResolvingCountry ( false ) ;
} ;
@ -480,7 +584,13 @@ export function QuestionPhone({
. then ( ( resolvedCode ) = > {
if ( ! isMounted ) return ;
if ( ! userInteractedRef . current ) {
setCodeValue ( resolvedCode || defaultCodeValue ) ;
const finalCode = resolvedCode || defaultCodeValue ;
setCodeValue ( finalCode ) ;
if ( phoneValue ) {
setPhoneValue ( ( prev ) = >
formatPhoneNumberAsYouType ( prev , finalCode ) ,
) ;
}
}
setIsResolvingCountry ( false ) ;
} )
@ -496,10 +606,12 @@ export function QuestionPhone({
isMounted = false ;
geoListeners . delete ( onGeoCodeResolved ) ;
} ;
} , [ hasExplicitValue , isResolvingCountry , defaultCodeValue ] ) ;
} , [ hasExplicitValue , isResolvingCountry , defaultCodeValue , phoneValue ] ) ;
const normalizedPhoneState = getNormalizedPhoneValue ( codeValue , phoneValue ) ;
const showInvalidState =
isTouched &&
! isFocused &&
codeValue . trim ( ) . length > 0 &&
phoneValue . trim ( ) . length > 0 &&
! normalizedPhoneState . isValid ;
@ -631,13 +743,19 @@ export function QuestionPhone({
if ( explicit ) {
setIsResolvingCountry ( false ) ;
setCodeValue ( nextValue . codeValue ) ;
const maxLen = getMaxLengthForCountry ( nextValue . codeValue ) ;
setPhoneValue ( nextValue . phoneValue . slice ( 0 , maxLen ) ) ;
const formatted = formatPhoneNumberAsYouType (
nextValue . phoneValue ,
nextValue . codeValue ,
) ;
setPhoneValue ( formatted ) ;
} else if ( value !== null && value !== undefined ) {
const resolvedCode = cachedCode || defaultCodeValue ;
setCodeValue ( resolvedCode ) ;
const maxLen = getMaxLengthForCountry ( resolvedCode ) ;
setPhoneValue ( nextValue . phoneValue . slice ( 0 , maxLen ) ) ;
const formatted = formatPhoneNumberAsYouType (
nextValue . phoneValue ,
resolvedCode ,
) ;
setPhoneValue ( formatted ) ;
}
lastCommittedValueRef . current = value ;
@ -664,12 +782,11 @@ export function QuestionPhone({
userInteractedRef . current = true ;
setIsResolvingCountry ( false ) ;
setManuallySelectedGeoCode ( selectedCode ) ;
const maxLen = getMaxLengthForCountry ( selectedCode ) ;
const truncatedPhone = phoneValue . slice ( 0 , maxLen ) ;
const reformatted = formatPhoneNumberAsYouType ( phoneValue , selectedCode ) ;
setCodeValue ( selectedCode ) ;
setPhoneValue ( truncatedPhone ) ;
updateStoredValue ( selectedCode , truncatedPhone ) ;
setPhoneValue ( reformatted ) ;
updateStoredValue ( selectedCode , reformatted ) ;
closeSheet ( ) ;
} ;
@ -768,19 +885,27 @@ export function QuestionPhone({
data - bwignore = "true"
data - form - type = "other"
disabled = { disabled }
placeholder = { question . extras . placeHolder ? . replace ( /^\+\d+\s*/ , "" ) }
placeholder = { dynamicPlaceholder }
value = { phoneValue }
maxLength = { getMaxLengthForCountry ( codeValue ) }
onFocus = { ( ) = > {
setIsFocused ( true ) ;
} }
onBlur = { ( ) = > {
setIsFocused ( false ) ;
setIsTouched ( true ) ;
} }
onChange = { ( event ) = > {
userInteractedRef . current = true ;
setIsResolvingCountry ( false ) ;
setManuallySelectedGeoCode ( codeValue ) ;
const nextPhoneValue = sanitizePhoneNumber ( event . target . value ) ;
const maxLen = getMaxLengthForCountry ( codeValue ) ;
const truncatedPhone = nextPhoneValue . slice ( 0 , maxLen ) ;
const formatted = formatPhoneNumberAsYouType (
event . target . value ,
codeValue ,
) ;
setPhoneValue ( truncatedPhone ) ;
updateStoredValue ( codeValue , truncatedPhone ) ;
setPhoneValue ( formatted ) ;
updateStoredValue ( codeValue , formatted ) ;
} }
dir = "ltr"
className = "h-full w-full border-0 bg-transparent p-0 text-left text-[15px] font-medium leading-none text-[#181818] tabular-nums outline-none placeholder:text-[#98A2B3]"
@ -789,7 +914,8 @@ export function QuestionPhone({
< / div >
{ showInvalidState ? (
< span className = "block group-10 font-semibold text-[#F2465F]" >
Enter a valid phone number with country code .
{ t [ "Enter a valid phone number with country code." ] ||
"Enter a valid phone number with country code." }
< / span >
) : null }