Conversation
|
@Benjtalkshow is attempting to deploy a commit to the Trustless Work Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughAdds client-side PDF export: new jspdf dependencies, a PDF-generation utility, and an "Export to PDF" button in TitleCard that triggers export using the current network and organized escrow data. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant TitleCard
participant NetworkCtx as NetworkContext
participant ExportUtil as exportEscrowToPDF
participant jsPDF
User->>TitleCard: Click "Export to PDF"
TitleCard->>NetworkCtx: read current network
TitleCard->>ExportUtil: call exportEscrowToPDF(organized, network)
ExportUtil->>jsPDF: assemble document (sections, tables, pagination)
jsPDF-->>ExportUtil: PDF blob
ExportUtil->>User: trigger download (filename with escrow ID + date)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/components/escrow/title-card.tsx (1)
28-32: Handle export failures explicitly in the click handler.Line 28-Line 32 calls export directly; if PDF generation/save throws, the user gets no feedback. A small
try/catchpath would improve reliability.✅ Suggested patch
const handleExportPDF = () => { - if (organized) { - exportEscrowToPDF(organized, currentNetwork); - } + if (!organized) return; + try { + exportEscrowToPDF(organized, currentNetwork); + } catch (error) { + console.error("Failed to export escrow PDF", error); + } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/escrow/title-card.tsx` around lines 28 - 32, handleExportPDF currently calls exportEscrowToPDF(organized, currentNetwork) directly and can throw without user feedback; wrap the call in a try/catch inside handleExportPDF, call exportEscrowToPDF only if organized is present, and on error log the exception and surface a user-facing message (e.g., via existing toast/notification/error state) so the user knows the export failed; reference the handleExportPDF function and exportEscrowToPDF call and ensure organized and currentNetwork are still passed through unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/escrow/title-card.tsx`:
- Around line 71-78: Add an explicit type attribute to the Export button to
prevent implicit form submission: in the JSX for the button that calls
handleExportPDF (the button containing <FileDown /> and "Export to PDF" text in
title-card.tsx), set type="button" on that <button> element so clicks only
trigger the export handler and not a form submit.
In `@src/utils/escrowExport.ts`:
- Line 14: The code creates multiple Date instances (timestamp = new
Date().toLocaleString()) and later calls new Date().toISOString() for the
filename, causing mismatched header vs filename times; replace both with a
single Date instance (e.g., create const now = new Date() once) and derive the
displayed timestamp (now.toLocaleString()) and the filename timestamp
(now.toISOString() or sanitized variant) from that same now object so header
metadata and filename always match; update references to timestamp and any
direct new Date().toISOString() calls in escrowExport.ts to use the shared
now-derived values.
- Around line 111-126: The milestones export omits each milestone's description;
update milestoneHead to include "Description" (for single-release:
["ID","Title","Description","Status","Approved"], for multi-release:
["ID","Title","Description","Amount","Status","Approved"]) and update the
milestoneBody build in the organized.milestones.map callback to include
m.description (use empty string if missing) right after m.title; when
organized.escrowType === "multi-release" insert m.amount (or "0.00") at the
position before status (i.e. adjust the splice index to account for the new
description field). Ensure you modify the milestoneHead and the base array
construction in the same function so headers and rows align.
- Around line 61-66: The statusData array in escrowExport (const statusData)
omits the escrow lifecycle state; update statusData to include a lifecycle entry
(e.g., add ["Lifecycle State", organized.lifecycle] or ["Lifecycle State",
organized.flags.lifecycle_state] depending on the existing property) alongside
the existing ["Progress", ...], ["Dispute State", ...], ["Release State", ...],
["Resolution State", ...] entries so the exported status block contains
lifecycle information and matches the other status labels.
---
Nitpick comments:
In `@src/components/escrow/title-card.tsx`:
- Around line 28-32: handleExportPDF currently calls
exportEscrowToPDF(organized, currentNetwork) directly and can throw without user
feedback; wrap the call in a try/catch inside handleExportPDF, call
exportEscrowToPDF only if organized is present, and on error log the exception
and surface a user-facing message (e.g., via existing toast/notification/error
state) so the user knows the export failed; reference the handleExportPDF
function and exportEscrowToPDF call and ensure organized and currentNetwork are
still passed through unchanged.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
bun.lockis excluded by!**/*.lockpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
package.jsonsrc/components/escrow/escrow-content.tsxsrc/components/escrow/title-card.tsxsrc/utils/escrowExport.ts
| <button | ||
| onClick={handleExportPDF} | ||
| className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium bg-primary cursor-pointer hover:bg-primary/10 border border-primary/20 rounded-lg text-primary-foreground transition-all duration-200 transform hover:scale-105 active:scale-95" | ||
| title="Export to PDF" | ||
| > | ||
| <FileDown className="h-4 w-4" /> | ||
| <span>Export to PDF</span> | ||
| </button> |
There was a problem hiding this comment.
Set explicit type="button" on the export action.
At Line 71, the button implicitly defaults to submit. If this component is ever rendered inside a form, clicking export can trigger unintended form submission.
✅ Suggested patch
{organized && (
<button
+ type="button"
onClick={handleExportPDF}
className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium bg-primary cursor-pointer hover:bg-primary/10 border border-primary/20 rounded-lg text-primary-foreground transition-all duration-200 transform hover:scale-105 active:scale-95"
title="Export to PDF"
>📝 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.
| <button | |
| onClick={handleExportPDF} | |
| className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium bg-primary cursor-pointer hover:bg-primary/10 border border-primary/20 rounded-lg text-primary-foreground transition-all duration-200 transform hover:scale-105 active:scale-95" | |
| title="Export to PDF" | |
| > | |
| <FileDown className="h-4 w-4" /> | |
| <span>Export to PDF</span> | |
| </button> | |
| <button | |
| type="button" | |
| onClick={handleExportPDF} | |
| className="flex items-center gap-2 px-3 py-1.5 text-sm font-medium bg-primary cursor-pointer hover:bg-primary/10 border border-primary/20 rounded-lg text-primary-foreground transition-all duration-200 transform hover:scale-105 active:scale-95" | |
| title="Export to PDF" | |
| > | |
| <FileDown className="h-4 w-4" /> | |
| <span>Export to PDF</span> | |
| </button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/escrow/title-card.tsx` around lines 71 - 78, Add an explicit
type attribute to the Export button to prevent implicit form submission: in the
JSX for the button that calls handleExportPDF (the button containing <FileDown
/> and "Export to PDF" text in title-card.tsx), set type="button" on that
<button> element so clicks only trigger the export handler and not a form
submit.
| network: NetworkType | ||
| ) => { | ||
| const doc = new jsPDF(); | ||
| const timestamp = new Date().toLocaleString(); |
There was a problem hiding this comment.
Use one timestamp source for both header metadata and filename.
Line 14/Line 26 uses locale-local time, while Line 160 uses UTC date from toISOString(). Around midnight this can produce mismatched dates between the visible report timestamp and the downloaded filename.
✅ Suggested patch
- const timestamp = new Date().toLocaleString();
+ const exportDate = new Date();
+ const timestamp = exportDate.toISOString();
@@
- doc.text(`Export Timestamp: ${timestamp}`, 14, 40);
+ doc.text(`Export Timestamp (UTC): ${timestamp}`, 14, 40);
@@
- const filename = `EscrowReport_${organized.properties.escrow_id.substring(0, 8)}_${new Date().toISOString().split('T')[0]}.pdf`;
+ const filename = `EscrowReport_${organized.properties.escrow_id.substring(0, 8)}_${timestamp.slice(0, 10)}.pdf`;Also applies to: 26-26, 160-160
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/escrowExport.ts` at line 14, The code creates multiple Date
instances (timestamp = new Date().toLocaleString()) and later calls new
Date().toISOString() for the filename, causing mismatched header vs filename
times; replace both with a single Date instance (e.g., create const now = new
Date() once) and derive the displayed timestamp (now.toLocaleString()) and the
filename timestamp (now.toISOString() or sanitized variant) from that same now
object so header metadata and filename always match; update references to
timestamp and any direct new Date().toISOString() calls in escrowExport.ts to
use the shared now-derived values.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
src/utils/escrowExport.ts (3)
67-72:⚠️ Potential issue | 🟠 MajorAdd lifecycle state to the Escrow Status block.
Line 67-Line 72 omits lifecycle state, which is part of the required status content.
🔧 Proposed fix
const statusData = [ + ["Lifecycle State", organized.properties.lifecycle_state || "N/A"], ["Progress", `${organized.progress}%`], ["Dispute State", organized.flags.dispute_flag], ["Release State", organized.flags.release_flag], ["Resolution State", organized.flags.resolved_flag], ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/escrowExport.ts` around lines 67 - 72, The Escrow Status block (statusData) is missing the lifecycle state entry; update the statusData array in escrowExport.ts to include a ["Lifecycle State", <value>] element using the appropriate source (prefer organized.flags.lifecycle_state or fallback to organized.lifecycle) so the lifecycle is shown alongside Progress, Dispute State, Release State, and Resolution State.
117-132:⚠️ Potential issue | 🟠 MajorInclude milestone description in exported milestones table.
Line 117-Line 132 omits
description, so milestones lose key context in the report.🔧 Proposed fix
const milestoneHead = organized.escrowType === "multi-release" - ? [["ID", "Title", "Amount", "Status", "Approved"]] - : [["ID", "Title", "Status", "Approved"]]; + ? [["ID", "Title", "Description", "Amount", "Status", "Approved"]] + : [["ID", "Title", "Description", "Status", "Approved"]]; const milestoneBody = organized.milestones.map((m: ParsedMilestone) => { const base = [ String(m.id + 1), m.title, + m.description || "", m.status, m.approved ? "Yes" : "No", ]; if (organized.escrowType === "multi-release") { - base.splice(2, 0, m.amount || "0.00"); + base.splice(3, 0, m.amount || "0.00"); } return base; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/escrowExport.ts` around lines 117 - 132, The exported milestones table omits the milestone description; update the milestoneHead and milestoneBody to include a "Description" column and the ParsedMilestone.description for each row. Modify milestoneHead so multi-release becomes ["ID","Title","Description","Amount","Status","Approved"] and single-release becomes ["ID","Title","Description","Status","Approved"]. In milestoneBody, insert m.description (or "" if missing) after the title (e.g., base.splice(2,0,m.description || "")), and adjust the multi-release amount insertion to splice at index 3 instead of 2 so the amount lands after the description (keep using organized.escrowType and ParsedMilestone/m identifiers to locate the code).
20-20:⚠️ Potential issue | 🟡 MinorUse one
Dateinstance for header and filename metadata.Line 20 and Line 166 derive time from different
Dateobjects, which can produce mismatched report timestamp vs filename date.🔧 Proposed fix
- const timestamp = new Date().toLocaleString(); + const exportDate = new Date(); + const timestamp = exportDate.toLocaleString(); + const fileDate = exportDate.toISOString().split("T")[0]; @@ - const filename = `EscrowReport_${organized.properties.escrow_id.substring(0, 8)}_${new Date().toISOString().split('T')[0]}.pdf`; + const filename = `EscrowReport_${organized.properties.escrow_id.substring(0, 8)}_${fileDate}.pdf`;Also applies to: 166-166
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/escrowExport.ts` at line 20, The code creates two separate Date objects for the report header and the export filename (the existing const timestamp and the later new Date() at the filename generation site), which can produce mismatched times; fix it by creating and using a single Date instance (e.g., const now = new Date()) at the top of the export flow and derive both the header timestamp (timestamp = now.toLocaleString()) and the filename date string (from the same now) so both header and filename use the identical moment; update usages of timestamp and the filename-building expression to read from that single now value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/escrow/EscrowDetails.tsx`:
- Around line 39-41: The file sets DEBUG to true unconditionally in
EscrowDetails (const DEBUG = true); change this to honor environment or build
flags (e.g., process.env.NODE_ENV !== 'production' or a specific
REACT_APP_DEBUG_ESCROW) so debug logging is enabled only in non-production
builds; update the DEBUG constant in EscrowDetails.tsx to read from that
env/config flag and ensure any debug logs gated by DEBUG remain unchanged.
In `@src/utils/escrowExport.ts`:
- Around line 107-115: The milestone table startY is computed from finalY
(finalY > 240) instead of whether a new page was actually added, which can place
the table too low; update the logic around the doc.addPage() call (where
milestonesY is checked) to set a boolean (e.g., pageAdded) or immediately
compute startY after adding the page, and then use that page-aware startY when
calling the table renderer (ensure the same fix is applied to the other
occurrence around the second milestones block), referencing the
milestonesY/finalY checks, doc.addPage(), and the startY/ table rendering calls
so startY reflects the real current page position.
---
Duplicate comments:
In `@src/utils/escrowExport.ts`:
- Around line 67-72: The Escrow Status block (statusData) is missing the
lifecycle state entry; update the statusData array in escrowExport.ts to include
a ["Lifecycle State", <value>] element using the appropriate source (prefer
organized.flags.lifecycle_state or fallback to organized.lifecycle) so the
lifecycle is shown alongside Progress, Dispute State, Release State, and
Resolution State.
- Around line 117-132: The exported milestones table omits the milestone
description; update the milestoneHead and milestoneBody to include a
"Description" column and the ParsedMilestone.description for each row. Modify
milestoneHead so multi-release becomes
["ID","Title","Description","Amount","Status","Approved"] and single-release
becomes ["ID","Title","Description","Status","Approved"]. In milestoneBody,
insert m.description (or "" if missing) after the title (e.g.,
base.splice(2,0,m.description || "")), and adjust the multi-release amount
insertion to splice at index 3 instead of 2 so the amount lands after the
description (keep using organized.escrowType and ParsedMilestone/m identifiers
to locate the code).
- Line 20: The code creates two separate Date objects for the report header and
the export filename (the existing const timestamp and the later new Date() at
the filename generation site), which can produce mismatched times; fix it by
creating and using a single Date instance (e.g., const now = new Date()) at the
top of the export flow and derive both the header timestamp (timestamp =
now.toLocaleString()) and the filename date string (from the same now) so both
header and filename use the identical moment; update usages of timestamp and the
filename-building expression to read from that single now value.
Closes #61
Objective
Implement a client-side PDF export feature that generates structured, audit-ready reports of the current escrow state without requiring backend changes or wallet interactions.
Technical Changes
Dependencies
Utility Layer
Created
src/utils/escrowExport.tsto handle:OrganizedEscrowDatainto tabular formats suitable for PDF rendering.UI Integration
TitleCardwith an "Export to PDF" action button.EscrowContentto pass organized data context to child components required for the export functionality.Enhancement
Result
Users can now export the current escrow state as a clean, structured PDF report directly from the interface, improving transparency and enabling easy sharing for audits or record keeping.
Proof
Summary by CodeRabbit
New Features
Chores