teqo-backup/src/lib/schemas/primitives.ts
Francisco Ignácio Fontoura Solla 26b2205c3a B154 — criar assessor inline no popover da lista de municípios
Closes #352

- POST /campanha/municipios/advisors aceita body de criação (name) na union;
  createMunicipalityAdvisorRecord cria campaignUser advisor (stub
  <slug>@criado.invalid, senha aleatória) e atribui ao município na mesma
  transação (cap 10 com rollback, lock municipality-advisors:{id})
- MunicipalityAdvisorCreateProvider compartilha o assessor criado entre linhas;
  opção "+ Criar assessor" e chip otimista com id temporário no control
- advisorNameSchema unificado em primitives.ts; predicado de placeholder cobre
  @criado.invalid (reset de senha e célula de e-mail tratam stubs inline)
- Destrava o spec e2e de municipios (falhas pré-existentes em main): key
  warning do fragment trailing de MunicipalityFilters; settle-wait da animação
  do Drawer no teste B136
2026-08-03 22:46:06 -03:00

51 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { z } from 'zod'
import { BRAZILIAN_PHONE_INVALID_MESSAGE, normalizeBrazilianPhone } from '@/lib/phone'
/**
* The assessor display-name rule, shared by the B19 account screen, the B154
* inline create and the wire contract of `POST /campanha/municipios/advisors`.
* Owned here (a leaf module) because `schemas/advisor.ts` already imports from
* `schemas/municipality.ts`, so the reverse import would close a cycle.
*/
export const advisorNameSchema = z.string().trim().min(2).max(160)
export const positiveRelationshipId = z.number().int().positive()
export const trimmedOptionalText = (maximum: number) =>
z
.string()
.trim()
.max(maximum)
.optional()
.transform((value) => (value === '' ? undefined : value))
export const trimmedNullableText = (maximum: number) =>
z
.union([z.string().trim().max(maximum), z.null()])
.optional()
.transform((value) => (value === '' ? null : value))
export const brazilianMobile = z.string().transform((value, context) => {
const phone = normalizeBrazilianPhone(value)
if (phone) return phone
context.addIssue({
code: 'custom',
message: BRAZILIAN_PHONE_INVALID_MESSAGE,
})
return z.NEVER
})
export const optionalPersistedEmail = z
.union([z.email('E-mail inválido.'), z.literal('')])
.optional()
.transform((value) => (value === '' ? undefined : value))
export const nullablePersistedEmail = z
.union([z.email('E-mail inválido.'), z.literal(''), z.null()])
.optional()
.transform((value) => (value === '' ? null : value))
/** Upper bound for any vote count field — one bound, two validation layers (P3-K, was 1_000_000 spelled ×6). */
export const MAX_VOTE_COUNT = 1_000_000