- MunicipalityListAdvisorsControl: stop advancing lastPropsIDsRef from a successful delta response — it exists only to detect a genuine external prop change, and writing it there made a later stale re-render of the pre-edit content look external and clobber optimistic state back to it. Also reconcile with the latest confirmed server set on the error path once every in-flight delta settles (previously only the success path did), and consolidate chip/tooltip name resolution onto the shared advisorEntriesFromIds helper instead of a bespoke lookup with an '#id' placeholder. - Extract campaignJsonMutationErrorResponse/parseCampaignJsonRequestBody (src/utilities/campaignJsonMutationRoute.ts) and adopt them in both municipios/advisors/route.ts and municipios/expected-votes/route.ts, removing the duplicated body-parse + error-mapping ladder. - Municipality.ts's validateMunicipalityAdvisors now uses uniqueRelationshipIds instead of re-inlining the Set+map+filter dance the helper was extracted for. Verified: tsc --noEmit, lint, format:check, check:cycles all clean; full unit suite (450 tests) and the municipality/advisor int suites pass. knip's pre-existing payload.config.ts load error is unrelated (P3). Playwright's spawned webServer failed to boot in this sandbox (unrelated module-resolution environment issue, reproduced even before this diff); manual pnpm dev boots fine and the change is covered by unit/int tests.
57 lines
1.8 KiB
TypeScript
57 lines
1.8 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
|
|
import {
|
|
CAMPAIGN_SESSION_EXPIRED_MESSAGE,
|
|
mapCampaignFormActionError,
|
|
} from '@/utilities/campaignFormActionError'
|
|
|
|
export type CampaignJsonErrorBody = { status: 'error'; message: string }
|
|
|
|
/**
|
|
* Shared shell for `POST /campanha/**` JSON mutation routes (list-popover
|
|
* auto-save): parses the body, mapping a malformed one to the same 400 both
|
|
* routes already used. Domain parsing/success payloads stay per-route.
|
|
*/
|
|
export const parseCampaignJsonRequestBody = async (
|
|
request: Request,
|
|
): Promise<
|
|
{ ok: true; body: unknown } | { ok: false; response: NextResponse<CampaignJsonErrorBody> }
|
|
> => {
|
|
try {
|
|
return { ok: true, body: await request.json() }
|
|
} catch {
|
|
return {
|
|
ok: false,
|
|
response: NextResponse.json(
|
|
{ status: 'error', message: 'Corpo da requisição inválido.' },
|
|
{ status: 400 },
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps a thrown error to the JSON error response: safe messages (incl. the
|
|
* two auth ones, appended once here) pass through as 400, an expired/missing
|
|
* session is 401, anything else collapses to `genericMessage`.
|
|
*/
|
|
export const campaignJsonMutationErrorResponse = (
|
|
error: unknown,
|
|
{ safeMessages, genericMessage }: { safeMessages: readonly string[]; genericMessage: string },
|
|
): NextResponse<CampaignJsonErrorBody> => {
|
|
const mapped = mapCampaignFormActionError({
|
|
error,
|
|
safeMessages: [...safeMessages, 'Autenticação necessária.', CAMPAIGN_SESSION_EXPIRED_MESSAGE],
|
|
genericMessage,
|
|
})
|
|
|
|
const isAuthError =
|
|
error instanceof Error &&
|
|
(error.message === 'Autenticação necessária.' ||
|
|
error.message === CAMPAIGN_SESSION_EXPIRED_MESSAGE)
|
|
|
|
return NextResponse.json(
|
|
{ status: 'error', message: mapped.message ?? genericMessage },
|
|
{ status: isAuthError ? 401 : 400 },
|
|
)
|
|
}
|