import { redirect } from 'next/navigation' import { ZodError } from 'zod' import { FormDataBoundaryError, validationFieldErrors } from '@/lib/formData' export type CampaignFormErrorState = { fieldErrors?: Record message?: string values?: Values revision?: number } export type CampaignFormActionState = CampaignFormErrorState & { status?: 'success' } export const CAMPAIGN_SESSION_EXPIRED_MESSAGE = 'Sessão expirada. Entre novamente.' export type MapCampaignFormActionErrorOptions = { error: unknown safeMessages?: readonly string[] genericMessage: string values?: Values revision?: number resolveBoundaryMessage?: (error: FormDataBoundaryError) => string } export const mapCampaignFormActionError = ( options: MapCampaignFormActionErrorOptions, ): CampaignFormErrorState => { 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 ({ execute, redirectTo, safeMessages, genericMessage, }: { execute: () => Promise redirectTo: (result: Result) => string safeMessages?: readonly string[] genericMessage: string }): Promise => { let target: string try { target = redirectTo(await execute()) } catch (error) { return mapCampaignFormActionError({ error, safeMessages, genericMessage }) } redirect(target) }