91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
/**
|
|
* 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}`
|
|
}
|