Files
pn-new-crm/src/lib/validators/i18n.ts
Matt Ciaccio 8699f81879
Some checks failed
Build & Push Docker Images / lint (push) Failing after 1m18s
Build & Push Docker Images / build-and-push (push) Has been skipped
chore(style): codebase em-dash sweep + minor layout polish
Replaces every em-dash and en-dash with regular ASCII hyphens
across comments, JSX strings, and dev-facing logs. Mostly cosmetic
but stops the inconsistent mix that crept in over the last few
months (some files used em-dashes in comments, others didn't,
some used both).

Bundles two small dashboard-layout tweaks that touch a couple of
already-modified files:
- (dashboard)/layout.tsx main padding goes from p-6 to pt-3 px-6
  pb-6 so page content sits closer to the topbar.
- Sidebar now receives the ports list it needs for the footer
  port switcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 22:57:01 +02:00

79 lines
3.3 KiB
TypeScript

/**
* 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));