161 lines
4.5 KiB
TypeScript
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
|