From 4bffdac1752a5295c6904cc7c01b6e201e40aa8a Mon Sep 17 00:00:00 2001 From: Codex Review Integration Date: Thu, 17 Sep 2026 00:13:54 -0300 Subject: [PATCH] fix(uplifts): restrict revoke to admins and quiet permission failures (SH-207) - Gate the Revoke action on the approved queue row and the detail modal to admin users; non-admins get a disabled button with a plain 'Only admins can revoke uplifts' tooltip and the dialog cannot open. - Stop nesting an h2 inside DialogTitle in the uplift detail modal (hydration error) by rendering the title text as a non-heading. - Handle work-order detail/uplift query 403s quietly in the detail modal: sections fall back to 'Unavailable' and the queries suppress the global error toast (QueryCache now honors suppressErrorToast). - Extract UpliftDecisionDialogs and openUpliftEvidence from the page to meet the changed-file maintainability gate (complexity 23 -> pass). --- .../_components/open-uplift-evidence.ts | 13 ++ .../_components/uplift-approvals-table.tsx | 20 ++- .../_components/uplift-decision-dialogs.tsx | 42 ++++++ .../_components/uplift-detail-modal.tsx | 62 ++++++-- src/app/(protected)/uplifts/index.tsx | 54 ++----- .../use-cases/use-work-order-detail.ts | 2 + .../use-cases/use-work-order-uplifts.ts | 2 + src/lib/query/query-client.ts | 58 ++++---- .../uplift-approvals-approved-tab.test.tsx | 33 +++++ .../uplift-detail-modal-permissions.test.tsx | 132 ++++++++++++++++++ .../uplifts/uplift-detail-modal.test.tsx | 26 +++- .../uplift-queue-decision-flow.test.tsx | 5 + src/test/lib/query/query-client.test.ts | 42 ++++++ 13 files changed, 412 insertions(+), 79 deletions(-) create mode 100644 src/app/(protected)/uplifts/_components/open-uplift-evidence.ts create mode 100644 src/app/(protected)/uplifts/_components/uplift-decision-dialogs.tsx create mode 100644 src/test/app/(protected)/uplifts/uplift-detail-modal-permissions.test.tsx create mode 100644 src/test/lib/query/query-client.test.ts diff --git a/src/app/(protected)/uplifts/_components/open-uplift-evidence.ts b/src/app/(protected)/uplifts/_components/open-uplift-evidence.ts new file mode 100644 index 00000000..2654fedd --- /dev/null +++ b/src/app/(protected)/uplifts/_components/open-uplift-evidence.ts @@ -0,0 +1,13 @@ +import { toast } from "react-toastify"; +import type { UpliftQueueItem } from "@/domain/uplifts/types/uplift"; +import { upliftsApi } from "@/domain/uplifts/api/uplifts-api"; + +export function openUpliftEvidence(row: UpliftQueueItem): void { + void upliftsApi + .downloadEvidence(row.id, row.evidenceFileName || "uplift-evidence") + .catch((error: unknown) => { + toast.error( + error instanceof Error ? error.message : "Unable to download evidence right now.", + ); + }); +} diff --git a/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx b/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx index 2ab99236..3c0f3e20 100644 --- a/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx +++ b/src/app/(protected)/uplifts/_components/uplift-approvals-table.tsx @@ -20,6 +20,7 @@ import { formatDateTime, getWaitTimeTextClass, timeSince, waitTimeColor } from " export type UpliftApprovalTab = "pending" | "approved"; const CLOSED_WO_TOOLTIP = "This work order is closed. Uplifts can no longer be revoked."; +const ADMIN_ONLY_REVOKE_TOOLTIP = "Only admins can revoke uplifts"; function workOrderLabel(row: UpliftQueueItem): string { return row.woNumber || row.dispatchNumber || "—"; @@ -119,23 +120,26 @@ function PendingRowActions({ function ApprovedRowActions({ row, + canRevoke, onRevoke, isDecisionPending, }: { row: UpliftQueueItem; + canRevoke: boolean; onRevoke: (row: UpliftQueueItem) => void; isDecisionPending: boolean; }) { const closed = row.workOrderClosed === true; + const tooltip = !canRevoke ? ADMIN_ONLY_REVOKE_TOOLTIP : closed ? CLOSED_WO_TOOLTIP : ""; return ( e.stopPropagation()}> - +