/** * Zod schemas wrapping the i18n primitives. Used by route handlers * and form-level validation so the same rules run client + server. */ import { z } from 'zod'; import { ISO_COUNTRIES } from '@/lib/i18n/countries'; import { isValidE164 } from '@/lib/i18n/phone'; import { isValidSubdivisionCode } from '@/lib/i18n/subdivisions'; // ─── Country ────────────────────────────────────────────────────────────────── /** ISO-3166-1 alpha-2, uppercase. */ export const countryIsoSchema = z .string() .length(2) .toUpperCase() .refine((c) => ISO_COUNTRIES.has(c), 'Unknown country code'); // ─── Phone ──────────────────────────────────────────────────────────────────── /** E.164 form, e.g. '+442079460958'. */ export const phoneE164Schema = z .string() .min(1) .refine((v) => isValidE164(v), 'Invalid phone number'); // ─── Timezone ───────────────────────────────────────────────────────────────── /** * IANA timezone (e.g. 'Europe/Warsaw'). Validates against * `Intl.supportedValuesOf('timeZone')` when available. Older Node * runtimes that lack the API fall back to a permissive shape check * (`Region/City`) so the validator never blocks the path. */ export const ianaTimezoneSchema = z .string() .min(1) .refine((tz) => { if (typeof Intl !== 'undefined' && 'supportedValuesOf' in Intl) { try { const supported = Intl.supportedValuesOf('timeZone') as string[]; if (supported.length > 0) return supported.includes(tz); } catch { // fall through } } return /^[A-Z][A-Za-z_+-]+\/[A-Za-z_+-]+/.test(tz); }, 'Unknown timezone'); // ─── Subdivision ────────────────────────────────────────────────────────────── /** ISO 3166-2 code, e.g. 'PL-MZ'. */ export const subdivisionIsoSchema = z .string() .min(2) .refine((code) => isValidSubdivisionCode(code), 'Unknown subdivision code'); // ─── Optional variants ──────────────────────────────────────────────────────── // Inline forms most callers will use — empty strings normalize to null // so the user clearing a field doesn't fail validation. export const optionalCountryIsoSchema = z .union([z.literal(''), z.null(), countryIsoSchema]) .transform((v) => (v === '' || v === null ? null : v)); export const optionalPhoneE164Schema = z .union([z.literal(''), z.null(), phoneE164Schema]) .transform((v) => (v === '' || v === null ? null : v)); export const optionalIanaTimezoneSchema = z .union([z.literal(''), z.null(), ianaTimezoneSchema]) .transform((v) => (v === '' || v === null ? null : v)); export const optionalSubdivisionIsoSchema = z .union([z.literal(''), z.null(), subdivisionIsoSchema]) .transform((v) => (v === '' || v === null ? null : v));