import { ParseError, isPossiblePhoneNumber, parsePhoneNumber, validatePhoneNumberLength, } from "libphonenumber-js" import { z } from "zod" const enum ParseErrorMessage { INVALID_COUNTRY = "INVALID_COUNTRY", INVALID_LENGTH = "INVALID_LENGTH", NOT_A_NUMBER = "NOT_A_NUMBER", TOO_LONG = "TOO_LONG", TOO_SHORT = "TOO_SHORT", } export function phoneValidator( msg = "Required field", invalidMsg = "Invalid type" ) { return z .string({ invalid_type_error: invalidMsg, required_error: msg }) .min(1, { message: msg }) .superRefine((value, ctx) => { if (value) { try { const phoneNumber = parsePhoneNumber(value) if (phoneNumber) { if (isPossiblePhoneNumber(value, phoneNumber.country)) { return validatePhoneNumberLength(value, phoneNumber.country) } else { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Please enter a valid phone number", }) } } } catch (error) { if (error instanceof ParseError) { /** * Only setup for when we need proper validation, * should probably move to .superRefine to be able * to return different messages depending on error. */ switch (error.message) { case ParseErrorMessage.INVALID_COUNTRY: ctx.addIssue({ code: z.ZodIssueCode.custom, message: "The country selected and country code doesn't match", }) break case ParseErrorMessage.INVALID_LENGTH: ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Please enter a valid phone number", }) break case ParseErrorMessage.NOT_A_NUMBER: ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Please enter a number", }) break case ParseErrorMessage.TOO_LONG: ctx.addIssue({ code: z.ZodIssueCode.custom, message: "The number you have entered is too long", }) break case ParseErrorMessage.TOO_SHORT: ctx.addIssue({ code: z.ZodIssueCode.custom, message: "The number you have entered is too short", }) break } } else { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "The number you have entered is not valid", }) } } } }) }