From 70494de42ca84feb6032cd661b116690147e747f Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Sat, 17 Oct 2026 19:34:12 +0200 Subject: [PATCH] feat(esign): delete finished signature requests Managers get a Delete request action on a completed, cancelled or declined request, through the backend's new esign.requests.delete (@polinetwork/backend 1.11.0). In-progress requests are cancelled first, and a pending delivery hides the action. After deleting, the page goes back to the requests list before reloading the loaders, since reloading the detail page would look the deleted request up again. --- package.json | 2 +- pnpm-lock.yaml | 10 ++--- src/features/esign/request-page.tsx | 47 ++++++++++++++++++++++-- src/features/esign/requests.functions.ts | 5 +++ tests/server-security.test.mjs | 1 + 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 6997db0..6ef6c58 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@base-ui/react": "^1.6.0", "@dnd-kit/react": "^0.4.0", - "@polinetwork/backend": "^1.10.0", + "@polinetwork/backend": "^1.11.0", "@t3-oss/env-core": "^0.13.10", "@tanstack/charts": "1.1.0", "@tanstack/react-charts": "1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a97a2b3..6361a0d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,8 +26,8 @@ importers: specifier: ^0.4.0 version: 0.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@polinetwork/backend': - specifier: ^1.10.0 - version: 1.10.0 + specifier: ^1.11.0 + version: 1.11.0 '@t3-oss/env-core': specifier: ^0.13.10 version: 0.13.11(typescript@6.0.3)(zod@4.3.5) @@ -1034,8 +1034,8 @@ packages: resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} - '@polinetwork/backend@1.10.0': - resolution: {integrity: sha512-V2nCsISh/hmRtTNgjXKlmtACtIKiWSEiVkhwbdMul+WeQ1MnG9VDoSga9F4NJIRBLQkf23HrIhB41/FdcE7hNA==} + '@polinetwork/backend@1.11.0': + resolution: {integrity: sha512-kYQAkFiT6fDoDPmrSS9LhC6dC2s0Io7XrH02br4KXr42mW4aPVdDUrWrchB6Vm2gU1eLwAvbbBUottLXKRB+9w==} '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -5952,7 +5952,7 @@ snapshots: '@pkgr/core@0.3.6': {} - '@polinetwork/backend@1.10.0': {} + '@polinetwork/backend@1.11.0': {} '@polka/url@1.0.0-next.29': {} diff --git a/src/features/esign/request-page.tsx b/src/features/esign/request-page.tsx index 027a1a3..0ee6fe7 100644 --- a/src/features/esign/request-page.tsx +++ b/src/features/esign/request-page.tsx @@ -1,6 +1,6 @@ -import { Link, useRouter } from "@tanstack/react-router" +import { Link, useNavigate, useRouter } from "@tanstack/react-router" import { useServerFn } from "@tanstack/react-start" -import { Ban, Download, Files, FolderUp, Mail, RotateCw, X } from "lucide-react" +import { Ban, Download, Files, FolderUp, Mail, RotateCw, Trash2, X } from "lucide-react" import { useEffect, useMemo, useState } from "react" import { @@ -17,6 +17,7 @@ import { appToast, PageBar, PageContent, useCan } from "@/components/shell" import { Button } from "@/components/ui/button" import { Textarea } from "@/components/ui/textarea" import type { EsignDrive, EsignPdfInfo, EsignRequestDetail } from "@/lib/api/types" +import { errorHasCode } from "@/lib/errors" import { formatDateTime } from "@/lib/format" import { cn } from "@/lib/utils" @@ -24,7 +25,7 @@ import { declineEsignRequest, getEsignDocumentContent, uploadSignedEsignDocument import { PdfViewer, usePdfBytes } from "./pdf-viewer" import { kindsLabel, type Place, pagesLabel, placesLeft } from "./places" import { RequestStatusBadge, SignerStatusBadge, signedProgress, YouChip } from "./request-status" -import { cancelEsignRequest, retryEsignDelivery } from "./requests.functions" +import { cancelEsignRequest, deleteEsignRequest, retryEsignDelivery } from "./requests.functions" import { SignWithCie } from "./sign-with-cie" import { SignatureBox } from "./signature-box" @@ -77,10 +78,12 @@ function downloadBytes(bytes: Uint8Array, fileName: string) { export function EsignRequestPage({ detail }: { detail: Detail }) { const router = useRouter() + const navigate = useNavigate() const canManage = useCan("esign:requests:manage") const contentFn = useServerFn(getEsignDocumentContent) const declineFn = useServerFn(declineEsignRequest) const cancelFn = useServerFn(cancelEsignRequest) + const deleteFn = useServerFn(deleteEsignRequest) const retryFn = useServerFn(retryEsignDelivery) const uploadSignedFn = useServerFn(uploadSignedEsignDocument) const [uploadingSigned, setUploadingSigned] = useState(false) @@ -149,6 +152,8 @@ export function EsignRequestPage({ detail }: { detail: Detail }) { }, [request]) const canCancel = request.status === "in_progress" && canManage + // A request in progress is cancelled first; a pending delivery still needs the request. + const canDelete = request.status !== "in_progress" && request.delivery?.status !== "pending" && canManage const pages = pdf?.pages ?? [] const [declineOpen, setDeclineOpen] = useState(false) @@ -522,6 +527,42 @@ export function EsignRequestPage({ detail }: { detail: Detail }) { }} /> )} + + {canDelete && ( + + Delete request + + } + onConfirm={async () => { + try { + await deleteFn({ data: { id: request.id } }) + } catch (error) { + console.error(error) + // Already gone: the outcome the user asked for. + if (!errorHasCode(error, "NOT_FOUND")) throw error + } + try { + // Leave first: reloading this page would look the deleted request up again. + await navigate({ to: "/dashboard/esign/requests" }) + await router.invalidate({ sync: true }) + } catch (error) { + console.error(error) + } + appToast.success("Request deleted.") + }} + /> + )} diff --git a/src/features/esign/requests.functions.ts b/src/features/esign/requests.functions.ts index 57c4688..4899ddc 100644 --- a/src/features/esign/requests.functions.ts +++ b/src/features/esign/requests.functions.ts @@ -89,6 +89,11 @@ export const cancelEsignRequest = createServerFn({ method: "POST" }) .validator(z.object({ id: z.uuid() })) .handler(({ data, context }) => context.backend.esign.requests.cancel.mutate(data)) +export const deleteEsignRequest = createServerFn({ method: "POST" }) + .middleware([esignRequestsManageMiddleware]) + .validator(z.object({ id: z.uuid() })) + .handler(({ data, context }) => context.backend.esign.requests.delete.mutate(data)) + export const retryEsignDelivery = createServerFn({ method: "POST" }) .middleware([esignRequestsManageMiddleware]) .validator(z.object({ id: z.uuid() })) diff --git a/tests/server-security.test.mjs b/tests/server-security.test.mjs index e82df16..1944c39 100644 --- a/tests/server-security.test.mjs +++ b/tests/server-security.test.mjs @@ -316,6 +316,7 @@ test("event handlers integrate protected server-function redirects with the rout "src/features/esign/request-page.tsx": [ "declineEsignRequest", "cancelEsignRequest", + "deleteEsignRequest", "retryEsignDelivery", "uploadSignedEsignDocument", ],