feat: implement input length constraints and enhanced validation for international phone numbers

This commit is contained in:
estifanos
2026-08-20 08:58:07 +00:00
parent caa209306f
commit bc31cec0b2
5 changed files with 66 additions and 13 deletions

View File

@@ -1,5 +1,5 @@
import { z } from 'zod';
import { AsYouType, getCountryCallingCode, isValidPhoneNumber, parsePhoneNumberFromString, type CountryCode } from 'libphonenumber-js';
import { AsYouType, Metadata, getCountryCallingCode, isValidPhoneNumber, parsePhoneNumberFromString, type CountryCode } from 'libphonenumber-js';
/**
* Accepts any international number in E.164 (`+<country><number>`) or a
@@ -10,8 +10,9 @@ export const phoneNumber = z
.string()
.trim()
.transform((v) => parsePhoneNumberFromString(v, 'ET')?.number ?? v)
.refine((v) => isValidPhoneNumber(v), {
message: 'Enter a valid phone number',
.superRefine((v, ctx) => {
if (!v) ctx.addIssue({ code: 'custom', message: 'Phone number is required' });
else if (!isValidPhoneNumber(v)) ctx.addIssue({ code: 'custom', message: 'Enter a valid phone number' });
});
/** Same rules, but blank is allowed. */
@@ -67,3 +68,21 @@ export function formatNational(digits: string, country: CountryCode): string {
const formatted = new AsYouType().input(prefix + digits);
return formatted.startsWith(prefix) ? formatted.slice(prefix.length).trimStart() : digits;
}
const metadata = new Metadata();
/** Longest national number the country's numbering plan allows. */
export function maxNationalLength(country: CountryCode): number {
metadata.selectNumberingPlan(country);
return Math.max(...(metadata.numberingPlan?.possibleLengths() ?? [15]));
}
/**
* True when the typed digits exceed the country's longest number. Judged on
* the national part once it parses, so a trunk prefix (0911223344) or typed
* country code (251911223344) isn't counted against the limit.
*/
export function exceedsMaxLength(digits: string, country: CountryCode): boolean {
const national = parsePhoneNumberFromString(digits, country)?.nationalNumber ?? digits;
return national.length > maxNationalLength(country);
}