/** * Phone-number helpers built on libphonenumber-js. * * Uses the default `min` build (~110 KB gz). The `/mobile` build * rejects landlines and reserved-range numbers, which is wrong for * a marina CRM where clients commonly give office numbers. * The `/max` build adds carrier/geocoding we don't need. */ import { AsYouType, parsePhoneNumberFromString, isValidPhoneNumber as libIsValid, getCountryCallingCode, type CountryCode as LibCountryCode, } from 'libphonenumber-js'; import type { CountryCode } from './countries'; export interface ParsedPhone { /** E.164 form, e.g. '+442079460958'. Null when the input isn't parseable. */ e164: string | null; /** ISO alpha-2 of the country the number was parsed against. */ country: CountryCode | null; /** Display-friendly national format, e.g. '020 7946 0958'. */ national: string | null; /** Display-friendly international format, e.g. '+44 20 7946 0958'. */ international: string | null; isValid: boolean; } const EMPTY: ParsedPhone = { e164: null, country: null, national: null, international: null, isValid: false, }; /** * Parse a raw user-typed phone string into a normalized record. * `defaultCountry` provides context when the input lacks a +country prefix. */ export function parsePhone(raw: string, defaultCountry?: CountryCode): ParsedPhone { const trimmed = raw.trim(); if (!trimmed) return EMPTY; try { const parsed = parsePhoneNumberFromString(trimmed, defaultCountry as LibCountryCode); if (!parsed) return EMPTY; return { e164: parsed.number, country: (parsed.country ?? null) as CountryCode | null, national: parsed.formatNational(), international: parsed.formatInternational(), isValid: parsed.isValid(), }; } catch { return EMPTY; } } /** * Format the in-progress digits with `AsYouType` for live typing. * Returns the formatted string in the country's national style. */ export function formatAsYouType(raw: string, country: CountryCode): string { const formatter = new AsYouType(country as LibCountryCode); return formatter.input(raw); } /** * Strict validation for zod / form layer. Accepts E.164 only. */ export function isValidE164(value: string): boolean { return libIsValid(value); } export function callingCodeFor(country: CountryCode): string { return `+${getCountryCallingCode(country as LibCountryCode)}`; }