import { useCallback, useRef, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Box, Button, Group, Image, Loader, Modal, Paper, Stack, Text, TextInput, } from "@mantine/core"; import { ArrowLeft, Download, FileSignature, Printer } from "lucide-react"; import toast from "react-hot-toast"; import { ContractSignSuccessModal } from "@/components/contracts/ContractSignSuccessModal"; import { ContractSignaturePad } from "@/components/bookings/ContractSignaturePad"; import { contractsService } from "@/services/contracts.service"; import { QUERY_KEYS } from "@/constants/QUERY_KEYS"; import { useAuth } from "@/auth/useAuth"; import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions"; /** * Staff contract preview + sign. Staff must open and read the generated * contract here before signing. */ export default function ContractViewPage() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const qc = useQueryClient(); const iframeRef = useRef(null); const { user } = useAuth(); const [signOpen, setSignOpen] = useState(false); const [successOpen, setSuccessOpen] = useState(false); const [signerName, setSignerName] = useState(""); const [signatureData, setSignatureData] = useState(null); const [drawNew, setDrawNew] = useState(false); const { data, isLoading, isError, refetch } = useQuery({ queryKey: [...QUERY_KEYS.CONTRACTS.byId(id ?? ""), "contract-view"], queryFn: () => contractsService.getContractView(id!), enabled: Boolean(id), }); const savedSignatureImage = data?.savedSignature?.signatureImageUrl ?? null; const usingSaved = Boolean(savedSignatureImage) && !drawNew; const signMutation = useMutation({ mutationFn: () => contractsService.signContract(id!, { role: "STAFF", signatureImageBase64: usingSaved ? (savedSignatureImage as string) : (signatureData ?? ""), signerDisplayName: signerName.trim(), consentText: "I confirm this contract on behalf of EDR.", }), onSuccess: () => { setSignOpen(false); setSuccessOpen(true); void refetch(); void qc.invalidateQueries({ queryKey: QUERY_KEYS.CONTRACTS.byId(id!) }); void qc.invalidateQueries({ queryKey: QUERY_KEYS.CONTRACTS.ROOT }); }, onError: () => toast.error("Failed to sign contract"), }); const handlePrint = () => iframeRef.current?.contentWindow?.print(); const downloadPdf = useCallback(async () => { if (!id) return; try { const blob = await contractsService.downloadContractDocument(id); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `contract-${data?.reference ?? id}.pdf`; a.click(); URL.revokeObjectURL(url); } catch { toast.error("Could not download contract PDF."); } }, [id, data?.reference]); const openSign = () => { setSignerName(data?.savedSignature?.signerDisplayName ?? ""); setSignatureData(null); setDrawNew(false); setSignOpen(true); }; const confirmSign = () => { if (!signerName.trim()) return; const image = usingSaved ? savedSignatureImage : signatureData; if (!image) return; signMutation.mutate(); }; if (isLoading) { return ( ); } if (isError || !data) { return ( Could not load contract. ); } // Counter-signature permission is split per freight type — a bulk signer must // not sign a container contract (API enforces the same on POST /contract/sign). const maySign = hasPermission( user, FREIGHT_PERMS.contracts.signStaff[ data.freightType === "BULK" ? "bulk" : "container" ], ); return ( {data.canSignStaff && maySign && ( )}