teqo-backup/src/utilities/campaignFormActionError.ts
Francisco Ignácio Fontoura Solla f23c2582ee Break up the god modules into deep domain modules
- campaignAccess.ts (990 lines, ~80 exports) is now a 130-line explicit
  re-export surface over 14 domain modules in src/utilities/access/
  (shared actor predicates + per-collection access); public surface
  AST-verified identical, import sites untouched
- actions/supporter.ts split: single-supporter lifecycle (407) vs the CSV
  import pipeline in supporterImport.ts (357); 'use server' re-export
  constraint honored with thin async delegates
- organization/stateDeputy clone twins deduped by POLICY:
  runStaffEntityMutation (fresh staff assert + unique-conflict translation)
  keeps each caller's concretely-typed Payload mutation
- runCampaignRedirectFormAction removes the copy-pasted try/map/redirect
  ladder from 4 create formActions (planos kept its genuinely custom flow)
- Plaza→Municipality identifier rename finished (context keys, list-label
  and leadership constants, fixture sequence, test constants)
- RefreshRouteOnSave (React component) moved out of utilities/ to components/
2026-07-23 23:44:45 -03:00

78 lines
2.2 KiB
TypeScript

import { redirect } from 'next/navigation'
import { ZodError } from 'zod'
import { FormDataBoundaryError, validationFieldErrors } from '@/lib/formData'
export type CampaignFormErrorState<Values> = {
fieldErrors?: Record<string, string[]>
message?: string
values?: Values
revision?: number
}
export type CampaignFormActionState = CampaignFormErrorState<undefined> & {
status?: 'success'
}
export const CAMPAIGN_SESSION_EXPIRED_MESSAGE = 'Sessão expirada. Entre novamente.'
export type MapCampaignFormActionErrorOptions<Values> = {
error: unknown
safeMessages?: readonly string[]
genericMessage: string
values?: Values
revision?: number
resolveBoundaryMessage?: (error: FormDataBoundaryError) => string
}
export const mapCampaignFormActionError = <Values>(
options: MapCampaignFormActionErrorOptions<Values>,
): CampaignFormErrorState<Values> => {
const {
error,
safeMessages = [],
genericMessage,
values,
revision,
resolveBoundaryMessage,
} = options
if (error instanceof FormDataBoundaryError) {
const message = resolveBoundaryMessage ? resolveBoundaryMessage(error) : error.message
return { fieldErrors: { [error.field]: [message] }, values, revision }
}
if (error instanceof ZodError) {
return { fieldErrors: validationFieldErrors(error), values, revision }
}
if (error instanceof Error && safeMessages.includes(error.message)) {
return { message: error.message, values, revision }
}
return { message: genericMessage, values, revision }
}
/**
* The shared create→redirect form-action ladder: run the mutation, map any
* failure through `mapCampaignFormActionError`, redirect on success. The
* redirect happens OUTSIDE the try so Next's control-flow error is never
* swallowed by the mapper.
*/
export const runCampaignRedirectFormAction = async <Result>({
execute,
redirectTo,
safeMessages,
genericMessage,
}: {
execute: () => Promise<Result>
redirectTo: (result: Result) => string
safeMessages?: readonly string[]
genericMessage: string
}): Promise<CampaignFormActionState> => {
let target: string
try {
target = redirectTo(await execute())
} catch (error) {
return mapCampaignFormActionError({ error, safeMessages, genericMessage })
}
redirect(target)
}