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>
79 lines
3.3 KiB
TypeScript
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));
|