Files
kandev/apps/web/components/task/task-move-error-message.ts
T
2026-09-13 12:20:23 +01:00

76 lines
2.8 KiB
TypeScript

/**
* Extraction for the reason a task move was refused.
*
* Deliberately free of React and of the alert primitives: the banner renders
* it, but so does the proceed-to-next-step toast in a chat hook, and a plain
* helper keeps that from dragging component modules into unrelated bundles.
*/
import { ApiError } from "@/lib/api/client";
type Translate = (key: string) => string;
const MOVE_ERROR_TRANSLATIONS: Record<string, string> = {
task_move_active_session: "task:taskMoveErrorActiveSession",
task_move_archived: "task:taskMoveErrorArchived",
task_move_different_workspace: "task:taskMoveErrorDifferentWorkspace",
task_move_workflow_step: "task:taskMoveErrorWorkflowStep",
task_move_wip_limit: "task:taskMoveErrorWipLimit",
};
const GENERIC_MOVE_ERROR_TRANSLATION = "task:taskMoveErrorGeneric";
const GENERIC_REORDER_ERROR_TRANSLATION = "task:taskReorderErrorGeneric";
function getTaskMoveErrorCode(error: unknown): string | null {
if (!(error instanceof ApiError) || !error.body || typeof error.body !== "object") return null;
const code = (error.body as { code?: unknown }).code;
return typeof code === "string" && code.trim() ? code : null;
}
export function getTaskMoveErrorMessage(
error: unknown,
fallback: string,
translate?: Translate,
): string {
if (translate && error instanceof ApiError) {
const code = getTaskMoveErrorCode(error);
return translate((code && MOVE_ERROR_TRANSLATIONS[code]) || GENERIC_MOVE_ERROR_TRANSLATION);
}
if (error instanceof Error && error.message.trim()) return error.message;
if (typeof error === "string" && error.trim()) return error;
return fallback;
}
/**
* Localized failure message for a rejected reorder request
* (REQ-TASKS-KANBAN-TASK-REORDERING-001.20). The reorder endpoint's only
* caller-facing failure codes (`invalid_reorder`; `step_changed` is handled
* silently before this is ever called) carry no code-specific copy, so this
* always resolves to the one generic message for an ApiError.
*/
export function getTaskReorderErrorMessage(
error: unknown,
fallback: string,
translate?: Translate,
): string {
if (translate && error instanceof ApiError) {
return translate(GENERIC_REORDER_ERROR_TRANSLATION);
}
if (error instanceof Error && error.message.trim()) return error.message;
if (typeof error === "string" && error.trim()) return error;
return fallback;
}
/**
* The detail worth rendering beneath the headline, or null when extraction fell
* back to the headline itself. Repeating the same sentence twice reads as a
* rendering bug and tells the user nothing the headline has not already said.
*/
export function getTaskMoveErrorDetail(
error: unknown,
title: string,
translate?: Translate,
): string | null {
const detail = getTaskMoveErrorMessage(error, title, translate);
return detail === title ? null : detail;
}