/** * Utilities to normalize and format phone numbers without relying on external libraries. * The helpers keep the behaviour permissive to avoid breaking existing flows while * still providing a single place where formatting rules live. */ /** Matches characters that should be kept when normalising a phone number. */ const PHONE_CHAR_PATTERN = /[^+\d]/g /** * Normalises a phone number by trimming whitespace, removing spacing/separators and * converting international prefixes written with `00` to their `+` variant. */ export const normalizePhone = (rawValue: string | null | undefined): string => { const trimmed = typeof rawValue === 'string' ? rawValue.trim() : '' if (!trimmed) { return '' } const cleaned = trimmed.replace(PHONE_CHAR_PATTERN, '') if (cleaned.startsWith('00')) { return `+${cleaned.slice(2)}` } return cleaned } /** * Formats a phone number by grouping digits by two and joining them with dots while * keeping any international prefix. The function remains tolerant to partially * entered numbers and returns an empty string for nullish inputs. */ export const formatPhone = (rawValue: string | null | undefined): string => { if (rawValue == null) { return '' } const normalized = normalizePhone(rawValue) if (!normalized) { return '' } if (normalized.startsWith('+33')) { let nationalNumber = normalized.slice(3) if (nationalNumber.startsWith('0')) { nationalNumber = nationalNumber.slice(1) } if (nationalNumber.length % 2 !== 0) { nationalNumber = `0${nationalNumber}` } const groups = nationalNumber.match(/\d{1,2}/g) ?? [] if (groups.length === 0) { return '+33' } return ['+33', ...groups].join('.') } const hasInternationalPrefix = normalized.startsWith('+') const prefix = hasInternationalPrefix ? normalized.slice(0, 1) : '' const digits = hasInternationalPrefix ? normalized.slice(1) : normalized const groups = digits.match(/\d{1,2}/g) ?? [] const grouped = groups.join('.') return prefix ? `${prefix}${grouped}` : grouped } /** * Masks a phone number for display purposes by replacing the middle digits with ·. * Useful for UI fragments where the full number should not be exposed. */ export const maskPhone = (rawValue: string | null | undefined): string => { const normalized = normalizePhone(rawValue) if (!normalized) { return '' } if (normalized.length <= 4) { return normalized } const start = normalized.slice(0, 2) const end = normalized.slice(-2) const maskedMiddle = '·'.repeat(Math.max(0, normalized.length - 4)) return `${start}${maskedMiddle}${end}` }