Skip to content

Conversation

@Jayrodri088
Copy link
Contributor

@Jayrodri088 Jayrodri088 commented Jan 22, 2026

PDF Export Feature for Escrow Viewer

Overview

This PR adds a comprehensive PDF export feature that allows users to generate and download detailed reports of escrow contracts. The feature includes professional formatting, branding, and all relevant escrow information.

Features Implemented

1. PDF Generation

  • Library: Uses jsPDF for client-side PDF generation
  • Format: A4 portrait orientation with proper margins
  • Network Support: Works with both testnet and mainnet networks

2. PDF Content Sections

Header Section

  • Trustless Work logo (loaded from /logo.png)
  • Company name and "Escrow Report" title
  • Network information (Testnet/Mainnet)
  • Export timestamp with formatted date and time
  • Dark blue header background with white text
  • Proper positioning to prevent text cutoff

Escrow Summary

  • Escrow ID
  • Title and Description
  • Escrow Type (Single-Release/Multi-Release)
  • Asset/Trustline information
  • Total Amount and Current Balance
  • Platform Fee
  • Trustless Work Fee (0.3% calculation)
  • Engagement ID (if available)

Escrow Status

  • Current Status (Resolved/Disputed/Released/Active)
  • Dispute Flag
  • Release Flag
  • Resolved Flag
  • Progress percentage

Assigned Roles

  • All assigned roles with mapped role names
  • Wallet addresses for each role
  • Handles empty roles gracefully

Milestones

  • Milestone number and title
  • Description
  • Amount (if available)
  • Status (Approved/Released/Disputed/Resolved/Pending)
  • Signer and Approver information (if available)
  • Visual separation between milestones

Footer

  • "Generated by Trustless Work Escrow Viewer" text
  • Website link (https://trustless.work)
  • Page numbers (Page X of Y)

3. Component Architecture

  • New Component: ExportPdfButton.tsx
    • Extracted button logic into separate component
    • Handles contract ID resolution with fallback chain
    • Proper error handling and user feedback
    • Maintains same functionality as inline implementation

4. Code Quality Improvements

  • Removed unnecessary comments from PDF export utility
  • Better code organization with separated concerns
  • Cleaner EscrowDetails.tsx component
  • Removed unused imports

Technical Details

PDF Export Utility (src/utils/pdf-export.ts)

  • Async function generateEscrowPdf() for PDF generation
  • Helper functions for page breaks, text wrapping, section headers, and key-value pairs
  • Logo loading with graceful fallback if image fails
  • Automatic page breaks for long content
  • Word wrapping for long text values
  • Proper spacing and typography throughout

Export Button Component (src/components/escrow/ExportPdfButton.tsx)

  • Props: organized, network, contractId, initialEscrowId
  • Contract ID resolution priority:
    1. organized.properties.escrow_id
    2. contractId prop
    3. initialEscrowId prop
  • Error handling with user-friendly alerts
  • Loading state handled by button component

Integration

  • Button appears next to "View Transaction History" button
  • Only visible when escrow data is loaded (organizedWithLive is available)
  • Uses outline button variant to match existing UI
  • Includes FileDown icon from lucide-react

File Changes

New Files

  • src/components/escrow/ExportPdfButton.tsx - Export button component
  • src/utils/pdf-export.ts - PDF generation utility

Modified Files

  • src/components/escrow/EscrowDetails.tsx - Integrated ExportPdfButton component

Dependencies

  • jspdf - Added to package.json for PDF generation

Testing Considerations

  • Test with both testnet and mainnet escrows
  • Verify PDF generation with various data scenarios:
    • Escrows with all fields populated
    • Escrows with missing optional fields
    • Escrows with no milestones
    • Escrows with no assigned roles
  • Test logo loading (should work if /logo.png exists in public folder)
  • Verify contract ID resolution with different data states
  • Test error handling when contract ID is missing
  • Verify PDF downloads correctly with proper filename format

User Experience

  • One-click PDF export
  • Professional, branded report format
  • All relevant escrow information included
  • Print-friendly layout with white background
  • Clear section separation and typography
  • Automatic filename: escrow-report-{contractId}-{date}.pdf
image

Closes: #39

Summary by CodeRabbit

  • New Features
    • Added an "Export to PDF" button on escrow details. Users can download a multi-section escrow report (summary, status, roles, milestones) with proper pagination and a readable filename.
  • Chores
    • Added a PDF generation dependency to support exporting reports.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Contributor

vercel bot commented Jan 22, 2026

@Jayrodri088 is attempting to deploy a commit to the Trustless Work Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

Adds client-side PDF export: introduces jspdf, a new ExportPdfButton UI component in EscrowDetails, and generateEscrowPdf utility that produces a multi-section escrow report (header, summary, status, roles, milestones) and triggers a download.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added dependency: "jspdf": "^4.0.0"
Escrow view integration
src/components/escrow/EscrowDetails.tsx
Imported and conditionally renders ExportPdfButton alongside transaction history when organizedWithLive is truthy
UI - Export button
src/components/escrow/ExportPdfButton.tsx
New component: full-width outline button that resolves contractId (from organized, contractId, or initialEscrowId), validates it, calls generateEscrowPdf, and handles errors via alerts/logging
PDF generation utility
src/utils/pdf-export.ts
New async generateEscrowPdf function using jsPDF to build a multi-section, paginated PDF (logo/header, escrow summary, status, roles, milestones), handles image loading/fallback, formatting, and deterministic filename

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ExportPdfButton
    participant generateEscrowPdf
    participant jsPDF
    participant Browser

    User->>ExportPdfButton: Click "Export to PDF"
    ExportPdfButton->>ExportPdfButton: Resolve/validate contractId
    ExportPdfButton->>generateEscrowPdf: Invoke with organized, network, contractId
    generateEscrowPdf->>generateEscrowPdf: Load logo image (async)
    generateEscrowPdf->>jsPDF: Create document and add sections (header, summary, status, roles, milestones, footers)
    generateEscrowPdf->>Browser: Trigger PDF download
    Browser->>User: Deliver PDF file
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

  • Trustless-Work/Product#157: Matches implementation of in-repo "Export to PDF" (adds jspdf, ExportPdfButton, and generateEscrowPdf).
  • Print Escrow Viewer to PDF – “Export Escrow Report” #39: Implements the requested "Print Escrow Viewer to PDF – Export Escrow Report" feature (button placement, multi-section report, export behavior).

Poem

🐰 A rabbit hops with a joyful leap,

PDFs now spring from data deep.
Escrows printed neat and bright,
Milestones marching, pages tight.
Hooray—reports take flight! ✨📄

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add PDF export functionality for escrow contracts' directly describes the main change—implementing PDF export for escrow contracts—which aligns with the core objective of the pull request.
Linked Issues check ✅ Passed The PR fully implements issue #39 requirements: exports PDF with proper header, escrow summary, status, roles, milestones, footer, professional layout, button placement near View Transaction History, network/testnet support, and proper data matching.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the PDF export feature as specified in issue #39; no unrelated modifications detected in package.json, new utilities, new component, or EscrowDetails integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/utils/pdf-export.ts`:
- Around line 74-90: The addKeyValue helper can overflow because it only calls
checkPageBreak once; modify it to iterate valueLines and before rendering each
line call checkPageBreak(8) (or appropriate height), and if a new page was
started re-render the key label (using doc.setFont/bold and doc.text(`${key}:`,
xPos, yPosition)) so the key appears above continued lines; ensure valueX,
margin, contentWidth calculations remain the same and update yPosition after
each drawn line as currently done.
🧹 Nitpick comments (1)
src/components/escrow/ExportPdfButton.tsx (1)

1-53: Add a loading/disabled state to prevent duplicate exports.
Multiple clicks can trigger parallel PDF generation and duplicate downloads. Consider a simple isExporting state to disable the button and provide user feedback.

♻️ Suggested update (loading state + disable)
+import { useState } from "react";
 import { Button } from "@/components/ui/button";
 import { FileDown } from "lucide-react";
 import { generateEscrowPdf } from "@/utils/pdf-export";
 import type { OrganizedEscrowData } from "@/mappers/escrow-mapper";
 import type { NetworkType } from "@/lib/network-config";
@@
 export function ExportPdfButton({
   organized,
   network,
   contractId,
   initialEscrowId,
 }: ExportPdfButtonProps) {
+  const [isExporting, setIsExporting] = useState(false);
   const handleExport = async () => {
+    if (isExporting) return;
     const contractIdToUse =
       organized.properties.escrow_id ||
       contractId ||
       initialEscrowId;
@@
-    try {
+    try {
+      setIsExporting(true);
       await generateEscrowPdf({
         organized,
         network,
         contractId: contractIdToUse,
       });
     } catch (error) {
       console.error("Error generating PDF:", error);
       alert("Error generating PDF. Please try again.");
+    } finally {
+      setIsExporting(false);
     }
   };
@@
     <Button
       onClick={handleExport}
       variant="outline"
       className="w-full inline-flex justify-center items-center gap-2"
       title="Export escrow data to PDF"
+      disabled={isExporting}
+      aria-busy={isExporting}
     >
       <FileDown className="h-4 w-4" />
-      Export to PDF
+      {isExporting ? "Exporting..." : "Export to PDF"}
     </Button>
   );
 }

Comment on lines +74 to +90
const addKeyValue = (key: string, value: string, indent = 0) => {
checkPageBreak(8);
const xPos = margin + indent;
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.setTextColor(60, 60, 60);
doc.text(`${key}:`, xPos, yPosition);

const valueX = xPos + 50;
doc.setFont('helvetica', 'normal');
doc.setTextColor(0, 0, 0);
const valueLines = doc.splitTextToSize(value, contentWidth - 50 - indent);
valueLines.forEach((line: string) => {
doc.text(line, valueX, yPosition);
yPosition += 5;
});
yPosition += 2;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent long value lines from overflowing the page.
addKeyValue only checks page space once, so multi-line values can bleed into the footer or off-page. Add per-line page-break handling (and re-render the key on a new page for context).

🧾 Proposed fix (per-line page breaks + key reprint)
-  const addKeyValue = (key: string, value: string, indent = 0) => {
-    checkPageBreak(8);
-    const xPos = margin + indent;
-    doc.setFontSize(10);
-    doc.setFont('helvetica', 'bold');
-    doc.setTextColor(60, 60, 60);
-    doc.text(`${key}:`, xPos, yPosition);
-    
-    const valueX = xPos + 50;
-    doc.setFont('helvetica', 'normal');
-    doc.setTextColor(0, 0, 0);
-    const valueLines = doc.splitTextToSize(value, contentWidth - 50 - indent);
-    valueLines.forEach((line: string) => {
-      doc.text(line, valueX, yPosition);
-      yPosition += 5;
-    });
-    yPosition += 2;
-  };
+  const addKeyValue = (key: string, value: string, indent = 0) => {
+    const xPos = margin + indent;
+    const valueX = xPos + 50;
+
+    const renderKey = () => {
+      doc.setFontSize(10);
+      doc.setFont('helvetica', 'bold');
+      doc.setTextColor(60, 60, 60);
+      doc.text(`${key}:`, xPos, yPosition);
+      doc.setFont('helvetica', 'normal');
+      doc.setTextColor(0, 0, 0);
+    };
+
+    checkPageBreak(8);
+    renderKey();
+
+    const valueLines = doc.splitTextToSize(value, contentWidth - 50 - indent);
+    valueLines.forEach((line: string) => {
+      if (checkPageBreak(5)) {
+        renderKey();
+      }
+      doc.text(line, valueX, yPosition);
+      yPosition += 5;
+    });
+    yPosition += 2;
+  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const addKeyValue = (key: string, value: string, indent = 0) => {
checkPageBreak(8);
const xPos = margin + indent;
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.setTextColor(60, 60, 60);
doc.text(`${key}:`, xPos, yPosition);
const valueX = xPos + 50;
doc.setFont('helvetica', 'normal');
doc.setTextColor(0, 0, 0);
const valueLines = doc.splitTextToSize(value, contentWidth - 50 - indent);
valueLines.forEach((line: string) => {
doc.text(line, valueX, yPosition);
yPosition += 5;
});
yPosition += 2;
const addKeyValue = (key: string, value: string, indent = 0) => {
const xPos = margin + indent;
const valueX = xPos + 50;
const renderKey = () => {
doc.setFontSize(10);
doc.setFont('helvetica', 'bold');
doc.setTextColor(60, 60, 60);
doc.text(`${key}:`, xPos, yPosition);
doc.setFont('helvetica', 'normal');
doc.setTextColor(0, 0, 0);
};
checkPageBreak(8);
renderKey();
const valueLines = doc.splitTextToSize(value, contentWidth - 50 - indent);
valueLines.forEach((line: string) => {
if (checkPageBreak(5)) {
renderKey();
}
doc.text(line, valueX, yPosition);
yPosition += 5;
});
yPosition += 2;
};
🤖 Prompt for AI Agents
In `@src/utils/pdf-export.ts` around lines 74 - 90, The addKeyValue helper can
overflow because it only calls checkPageBreak once; modify it to iterate
valueLines and before rendering each line call checkPageBreak(8) (or appropriate
height), and if a new page was started re-render the key label (using
doc.setFont/bold and doc.text(`${key}:`, xPos, yPosition)) so the key appears
above continued lines; ensure valueX, margin, contentWidth calculations remain
the same and update yPosition after each drawn line as currently done.

@Jayrodri088
Copy link
Contributor Author

Hello @techrebelgit still waiting on a review

@Jayrodri088 Jayrodri088 force-pushed the feature/escrow-pdf-export branch from 9d93425 to a578bbe Compare January 24, 2026 16:54
@Jayrodri088 Jayrodri088 reopened this Jan 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Print Escrow Viewer to PDF – “Export Escrow Report”

1 participant