teqo-backup/src/lib/campaignJsonRequest.ts
Francisco Ignácio Fontoura Solla 5f11960c3a B32+: share the campaign JSON route wrapper, fetch helper and cell auto-save.
`campaignJsonMutationRoute` now builds every JSON POST route under /campanha —
`isSameOriginRequest` had been copied into five of them, so the CSRF policy was
failing open by omission; a convention guard sweeps all of `src/app` and names
its three exceptions. `postCampaignJson` is the only place that spells
`credentials: 'same-origin'`, and `useCampaignCellAutosave` is the debounce /
abort / revert / flush-on-close machine the quick-edit cells shipped three
times.

The shell keeps the `aria-live` region outside the Popover/Drawer, since closing
is what commits and it was unmounting the node that would announce the result;
`variant="sheet"` took a `footer` slot, which let the signal control drop the
overlay it only kept for one button.

Two bugs the extraction made visible: a revert issued while a different payload
was in flight was dropped silently, and `change()` never cleared a past failure,
which outranks everything the live region would otherwise say.
2026-07-28 08:07:29 -03:00

25 lines
976 B
TypeScript

/**
* Transport half of the list cells' quick edits: the preamble every control
* repeated around `fetch` (same-origin credentials, JSON headers, the cast of
* the discriminated response). Deliberately knows nothing about revert, retry
* or debounce — that is `useCampaignCellAutosave`'s, or the caller's.
*
* `ok` travels next to the payload because a route can answer a real body with
* a non-200 status (`engagement-level` returns its `blocked` state as 409), so
* the two are not the same question.
*/
export const postCampaignJson = async <TResponse>(
url: string,
body: unknown,
signal?: AbortSignal,
): Promise<{ ok: boolean; payload: TResponse }> => {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
credentials: 'same-origin',
signal,
body: JSON.stringify(body),
})
return { ok: response.ok, payload: (await response.json()) as TResponse }
}