mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
- 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/
78 lines
2.2 KiB
TypeScript
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)
|
|
}
|