profit-planet-frontend/src/app/components/phone/telephoneInput.tsx
2026-01-13 19:01:18 +01:00

161 lines
4.5 KiB
TypeScript

'use client'
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
InputHTMLAttributes,
} from 'react'
import { createIntlTelInput, IntlTelInputInstance } from '../../utils/phoneUtils'
export type TelephoneInputHandle = {
getNumber: () => string
isValid: () => boolean
}
interface TelephoneInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
/** e.g. "de" */
initialCountry?: string
}
/**
* Reusable telephone input with intl-tel-input.
* Always takes full available width.
*/
const TelephoneInput = forwardRef<TelephoneInputHandle, TelephoneInputProps>(
({ initialCountry = (process.env.NEXT_PUBLIC_GEO_FALLBACK_COUNTRY || 'DE').toLowerCase(), ...rest }, ref) => {
const inputRef = useRef<HTMLInputElement | null>(null)
const itiRef = useRef<IntlTelInputInstance | null>(null)
useEffect(() => {
let disposed = false
let instance: IntlTelInputInstance | null = null
const setup = async () => {
try {
console.log('[TelephoneInput] setup() start for', {
id: rest.id,
name: rest.name,
initialCountry,
})
if (!inputRef.current) {
console.warn('[TelephoneInput] setup() aborted: inputRef is null', {
id: rest.id,
name: rest.name,
})
return
}
instance = await createIntlTelInput(inputRef.current, {
initialCountry,
nationalMode: true,
strictMode: true,
autoPlaceholder: 'aggressive',
validationNumberTypes: ['MOBILE'],
})
if (disposed) {
console.log('[TelephoneInput] setup() finished but component is disposed, destroying instance', {
id: rest.id,
name: rest.name,
})
instance.destroy()
return
}
itiRef.current = instance
console.log('[TelephoneInput] intl-tel-input instance attached to input', {
id: rest.id,
name: rest.name,
})
} catch (e) {
console.error('[TelephoneInput] Failed to init intl-tel-input:', e)
}
}
setup()
return () => {
disposed = true
if (instance) {
console.log('[TelephoneInput] Destroying intl-tel-input instance for', {
id: rest.id,
name: rest.name,
})
instance.destroy()
if (itiRef.current === instance) itiRef.current = null
}
}
}, [initialCountry, rest.id, rest.name])
useImperativeHandle(ref, () => ({
getNumber: () => {
const raw = inputRef.current?.value || ''
if (itiRef.current) {
const intl = itiRef.current.getNumber()
console.log('[TelephoneInput] getNumber()', {
id: rest.id,
name: rest.name,
raw,
intl,
})
return intl
}
console.warn(
'[TelephoneInput] getNumber() called before intl-tel-input ready, returning raw value',
{ id: rest.id, name: rest.name, raw }
)
return raw
},
isValid: () => {
if (!itiRef.current) {
const raw = inputRef.current?.value || ''
console.warn('[TelephoneInput] isValid() called before intl-tel-input ready', {
id: rest.id,
name: rest.name,
raw,
})
return false
}
const instance = itiRef.current
const intl = instance.getNumber()
const valid = instance.isValidNumber()
const errorCode = typeof instance.getValidationError === 'function'
? instance.getValidationError()
: undefined
const country = typeof instance.getSelectedCountryData === 'function'
? instance.getSelectedCountryData()
: undefined
console.log('[TelephoneInput] isValid() check', {
id: rest.id,
name: rest.name,
intl,
valid,
errorCode,
country,
})
return valid
},
}))
return (
<div className="w-full">
<input
ref={inputRef}
type="tel"
className={`w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent transition-colors text-primary ${rest.className || ''}`}
{...rest}
/>
</div>
)
}
)
TelephoneInput.displayName = 'TelephoneInput'
export default TelephoneInput