@better-auth-ui/core
Version:
Authentication components and data utilities for [Better Auth](https://better-auth.com), available for React and Solid.
96 lines (85 loc) • 2.47 kB
text/typescript
import {
AsYouType,
type CountryCode,
getCountries,
getCountryCallingCode,
parsePhoneNumberFromString
} from "libphonenumber-js/min"
export type PhoneNumberCountryCode = CountryCode
export type PhoneNumberCountry = {
callingCode: string
code: PhoneNumberCountryCode
flag: string
label: string
}
export type PhoneNumberValue = {
country: PhoneNumberCountryCode
display: string
e164?: string
isValid: boolean
}
export type PhoneNumberAdapter = {
format: (value: string, country: PhoneNumberCountryCode) => string
getCountry: (value: string) => PhoneNumberCountryCode | undefined
normalize: (
value: string,
country: PhoneNumberCountryCode
) => string | undefined
}
export const defaultPhoneNumberAdapter: PhoneNumberAdapter = {
format(value, country) {
if (!value.trim()) return ""
return new AsYouType(
value.trim().startsWith("+") ? undefined : country
).input(value)
},
getCountry(value) {
return parsePhoneNumberFromString(value)?.country
},
normalize(value, country) {
const phoneNumber = parsePhoneNumberFromString(value, country)
return phoneNumber?.isValid() ? phoneNumber.number : undefined
}
}
export function createPhoneNumberValue(
value: string,
country: PhoneNumberCountryCode,
adapter: PhoneNumberAdapter = defaultPhoneNumberAdapter
): PhoneNumberValue {
const display = adapter.format(value, country)
const e164 = adapter.normalize(display, country)
const resolvedCountry = adapter.getCountry(e164 ?? display) ?? country
return {
country: resolvedCountry,
display,
e164,
isValid: Boolean(e164)
}
}
export function getPhoneNumberCountries(
locale?: string,
countryCodes: readonly PhoneNumberCountryCode[] = getCountries()
): PhoneNumberCountry[] {
let displayNames: Intl.DisplayNames | undefined
let resolvedLocale = locale
try {
displayNames = new Intl.DisplayNames(locale ? [locale] : undefined, {
type: "region"
})
} catch {
resolvedLocale = "en"
displayNames = new Intl.DisplayNames(["en"], { type: "region" })
}
return countryCodes
.map((code) => ({
callingCode: `+${getCountryCallingCode(code)}`,
code,
flag: String.fromCodePoint(
...Array.from(code, (character) => character.charCodeAt(0) + 127397)
),
label: displayNames.of(code) ?? code
}))
.sort((left, right) =>
left.label.localeCompare(right.label, resolvedLocale)
)
}