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", ],