// Shared form-field schemas for numeric text inputs (single source — the // Number-based validation deliberately rejects trailing garbage like '12abc', // which parseFloat would silently coerce to 12). import { z } from 'zod'; /** Required numeric text input (>= 0). */ export const numericString = z .string() .refine((v) => v.trim() !== '' && !Number.isNaN(Number(v)) && Number(v) >= 0, { message: 'Must be a number >= 0', }); /** Optional numeric text input — empty maps to null on the wire. */ export const optionalNumericString = z .string() .refine((v) => v.trim() === '' || (!Number.isNaN(Number(v)) && Number(v) >= 0), { message: 'Must be a number >= 0', }); /** Convert an optionalNumericString form value to the wire number|null. */ export function toNumberOrNull(value: string): number | null { return value.trim() !== '' ? Number(value) : null; }