81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
|
|
/**
|
||
|
|
* 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)}`;
|
||
|
|
}
|