fix(#338): Route all state-changing fetch() calls through API client

- Replace raw fetch() with apiPost/apiPatch/apiDelete in:
  - ImportExportActions.tsx: POST for file imports
  - KanbanBoard.tsx: PATCH for task status updates
  - ActiveProjectsWidget.tsx: POST for widget data fetches
  - useLayouts.ts: POST/PATCH/DELETE for layout management
- Add apiPostFormData() method to API client for FormData uploads
- Ensures CSRF token is included in all state-changing requests
- Update tests to mock CSRF token fetch for API client usage

Refs #338

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-05 17:06:23 -06:00
parent 5ae07f7a84
commit 344e5df3bb
7 changed files with 198 additions and 119 deletions

View File

@@ -8,6 +8,7 @@ import type { DragEndEvent, DragStartEvent } from "@dnd-kit/core";
import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
import { KanbanColumn } from "./KanbanColumn";
import { TaskCard } from "./TaskCard";
import { apiPatch } from "@/lib/api/client";
interface KanbanBoardProps {
tasks: Task[];
@@ -93,19 +94,9 @@ export function KanbanBoard({ tasks, onStatusChange }: KanbanBoardProps): React.
const task = (tasks || []).find((t) => t.id === taskId);
if (task && task.status !== newStatus) {
// Call PATCH /api/tasks/:id to update status
// Call PATCH /api/tasks/:id to update status (using API client for CSRF protection)
try {
const response = await fetch(`/api/tasks/${taskId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ status: newStatus }),
});
if (!response.ok) {
throw new Error(`Failed to update task status: ${response.statusText}`);
}
await apiPatch(`/api/tasks/${taskId}`, { status: newStatus });
// Optionally call the callback for parent component to refresh
if (onStatusChange) {