← back to Jill Website
src/utils/phoneFormatter.ts
150 lines
/**
* Phone number formatting utilities
* Handles US and international phone number formatting
*/
export interface PhoneValidationResult {
isValid: boolean;
formatted?: string;
error?: string;
}
/**
* Format a phone number to (555) 123-4567 format
* Supports various input formats:
* - 5551234567
* - 555-123-4567
* - (555) 123-4567
* - +1 555 123 4567
* - +15551234567
*/
export function formatPhoneNumber(phone: string): string {
if (!phone) {
return '';
}
// Remove all non-digit characters
const digits = phone.replace(/\D/g, '');
// Handle different digit lengths
if (digits.length === 10) {
// US number without country code: 5551234567 -> (555) 123-4567
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
} else if (digits.length === 11 && digits.startsWith('1')) {
// US number with country code: 15551234567 -> (555) 123-4567
return `(${digits.slice(1, 4)}) ${digits.slice(4, 7)}-${digits.slice(7)}`;
} else if (digits.length > 11) {
// International number: keep country code separate
// Example: +50626824242 -> +506 2682-4242
const countryCode = digits.slice(0, digits.length - 10);
const localNumber = digits.slice(-10);
return `+${countryCode} ${localNumber.slice(0, 4)}-${localNumber.slice(4)}`;
} else if (digits.length >= 7) {
// Partial number with at least 7 digits: format as much as possible
const areaCode = digits.slice(0, 3);
const prefix = digits.slice(3, 6);
const line = digits.slice(6);
if (digits.length === 7) {
return `${prefix}-${line}`;
} else {
return `(${areaCode}) ${prefix}-${line}`;
}
}
// Return original if less than 7 digits (can't format meaningfully)
return phone;
}
/**
* Convert formatted phone number to tel: link format
* Removes all formatting and keeps only digits and leading +
*/
export function formatPhoneForTel(phone: string): string {
if (!phone) {
return '';
}
// Keep only digits and leading +
const cleaned = phone.replace(/[^\d+]/g, '');
// If starts with +, keep it; otherwise add +1 for US numbers if 10 digits
if (cleaned.startsWith('+')) {
return cleaned;
}
const digits = cleaned.replace(/\D/g, '');
if (digits.length === 10) {
return `+1${digits}`;
} else if (digits.length === 11 && digits.startsWith('1')) {
return `+${digits}`;
}
return `+${digits}`;
}
/**
* Validate phone number format
* Accepts US and international numbers with various formats
*/
export function validatePhoneNumber(phone: string): PhoneValidationResult {
if (!phone || typeof phone !== 'string') {
return {
isValid: false,
error: 'Phone number is required'
};
}
const trimmed = phone.trim();
if (trimmed.length === 0) {
return {
isValid: false,
error: 'Phone number cannot be empty'
};
}
// Remove all non-digit characters to count digits
const digits = trimmed.replace(/\D/g, '');
// Must have at least 7 digits (minimum for a valid phone number)
if (digits.length < 7) {
return {
isValid: false,
error: 'Phone number must have at least 7 digits'
};
}
// Maximum 15 digits (international standard E.164)
if (digits.length > 15) {
return {
isValid: false,
error: 'Phone number cannot exceed 15 digits'
};
}
// Check for valid characters (digits, spaces, dashes, parentheses, plus)
const validCharsRegex = /^[\d\s\-\(\)\+\.]+$/;
if (!validCharsRegex.test(trimmed)) {
return {
isValid: false,
error: 'Phone number contains invalid characters'
};
}
// Format the phone number
const formatted = formatPhoneNumber(trimmed);
return {
isValid: true,
formatted
};
}
/**
* Check if a phone number is valid (boolean only)
*/
export function isValidPhoneNumber(phone: string): boolean {
return validatePhoneNumber(phone).isValid;
}