Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions frontend/src/components/InvoicePreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import PaidIcon from "@mui/icons-material/CheckCircle";
import UnpaidIcon from "@mui/icons-material/Pending";
import CancelIcon from "@mui/icons-material/Cancel";
import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange";
import { formatInvoiceDate } from "../utils/formatDate";

const InvoicePreview = ({
invoice,
Expand Down Expand Up @@ -293,23 +294,15 @@ const InvoicePreview = ({
Issued:
</span>
<span className="text-gray-600">
{new Date(invoice.issueDate).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})}
{formatInvoiceDate(invoice.issueDate)}
</span>
</div>
<div className="flex items-center">
<span className="font-semibold text-gray-700 mr-2 min-w-[50px]">
Due:
</span>
<span className="text-gray-600">
{new Date(invoice.dueDate).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})}
{formatInvoiceDate(invoice.dueDate)}
</span>
</div>
</div>
Expand Down
18 changes: 4 additions & 14 deletions frontend/src/page/BatchPayment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import html2canvas from "html2canvas";
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import { decryptToString } from "@lit-protocol/encryption/src/lib/encryption.js";
import { LIT_ABILITY, LIT_NETWORK } from "@lit-protocol/constants";
import { formatInvoiceDate, formatDateTime } from "../utils/formatDate";
import {
createSiweMessageWithRecaps,
generateAuthSig,
Expand Down Expand Up @@ -822,11 +823,6 @@ function BatchPayment() {
)}`;
};

const formatDate = (issueDate) => {
const date = new Date(issueDate);
return date.toLocaleString();
};

const unpaidInvoices = receivedInvoices.filter(
(inv) => !inv.isPaid && !inv.isCancelled
);
Expand Down Expand Up @@ -1186,7 +1182,7 @@ function BatchPayment() {
)}
</td>
<td className="px-6 py-4 text-sm text-gray-600">
{formatDate(invoice.issueDate)}
{formatInvoiceDate(invoice.issueDate)}
</td>
<td className="px-6 py-4">
<div className="flex space-x-2">
Expand Down Expand Up @@ -1482,16 +1478,10 @@ function BatchPayment() {
<div className="mb-6">
<div className="flex justify-between text-sm text-gray-500 mb-2">
<span>
Issued:{" "}
{new Date(
drawerState.selectedInvoice.issueDate
).toLocaleDateString()}
Issued: {formatInvoiceDate(drawerState.selectedInvoice.issueDate)}
</span>
<span>
Due:{" "}
{new Date(
drawerState.selectedInvoice.dueDate
).toLocaleDateString()}
Due: {formatInvoiceDate(drawerState.selectedInvoice.dueDate)}
</span>
</div>
</div>
Expand Down
18 changes: 4 additions & 14 deletions frontend/src/page/ReceivedInvoice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import CloseIcon from "@mui/icons-material/Close";
import ErrorIcon from "@mui/icons-material/Error";
import { useTokenList } from "@/hooks/useTokenList";
import WalletConnectionAlert from "@/components/WalletConnectionAlert";
import { formatInvoiceDate, formatDateTime } from "@/utils/formatDate";

const columns = [
{ id: "select", label: "", minWidth: 50 },
Expand Down Expand Up @@ -883,11 +884,6 @@ function ReceivedInvoice() {
)}`;
};

const formatDate = (issueDate) => {
const date = new Date(issueDate);
return date.toLocaleString();
};

const unpaidInvoices = receivedInvoices.filter(
(inv) => !inv.isPaid && !inv.isCancelled
);
Expand Down Expand Up @@ -1404,7 +1400,7 @@ function ReceivedInvoice() {
</TableCell>
<TableCell>
<span className="text-sm text-gray-600">
{formatDate(invoice.issueDate)}
{formatDateTime(invoice.issueDate)}
</span>
</TableCell>

Expand Down Expand Up @@ -1780,16 +1776,10 @@ function ReceivedInvoice() {
<div className="mb-6">
<div className="flex justify-between text-sm text-gray-500 mb-2">
<span>
Issued:{" "}
{new Date(
drawerState.selectedInvoice.issueDate
).toLocaleDateString()}
Issued: {formatInvoiceDate(drawerState.selectedInvoice.issueDate)}
</span>
<span>
Due:{" "}
{new Date(
drawerState.selectedInvoice.dueDate
).toLocaleDateString()}
Due: {formatInvoiceDate(drawerState.selectedInvoice.dueDate)}
</span>
</div>
</div>
Expand Down
18 changes: 4 additions & 14 deletions frontend/src/page/SentInvoice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import CancelIcon from "@mui/icons-material/Cancel";
import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange";
import { useTokenList } from "@/hooks/useTokenList";
import WalletConnectionAlert from "@/components/WalletConnectionAlert";
import { formatInvoiceDate, formatDateTime } from "@/utils/formatDate";

const columns = [
{ id: "fname", label: "Client", minWidth: 120 },
Expand Down Expand Up @@ -417,11 +418,6 @@ function SentInvoice() {
)}`;
};

const formatDate = (issueDate) => {
const date = new Date(issueDate);
return date.toLocaleString();
};

return (
<>
<div className="flex justify-center">
Expand Down Expand Up @@ -613,7 +609,7 @@ function SentInvoice() {
{/* Date Column */}
<TableCell>
<span className="text-sm text-gray-600">
{formatDate(invoice.issueDate)}
{formatInvoiceDate(invoice.issueDate)}
</span>
</TableCell>

Expand Down Expand Up @@ -922,16 +918,10 @@ function SentInvoice() {
<div className="mb-6">
<div className="flex justify-between text-sm text-gray-500 mb-2">
<span>
Issued:{" "}
{new Date(
drawerState.selectedInvoice.issueDate
).toLocaleDateString()}
Issued: {formatInvoiceDate(drawerState.selectedInvoice.issueDate)}
</span>
<span>
Due:{" "}
{new Date(
drawerState.selectedInvoice.dueDate
).toLocaleDateString()}
Due: {formatInvoiceDate(drawerState.selectedInvoice.dueDate)}
</span>
</div>
</div>
Expand Down
68 changes: 68 additions & 0 deletions frontend/src/utils/formatDate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Returns the user's local UTC offset string, e.g. "UTC+5:30" or "UTC-8:00"
*/
function getUTCOffset() {
const offset = -new Date().getTimezoneOffset(); // minutes, sign flipped
const sign = offset >= 0 ? "+" : "-";
const abs = Math.abs(offset);
const hours = Math.floor(abs / 60);
const minutes = abs % 60;
return `UTC${sign}${hours}:${minutes.toString().padStart(2, "0")}`;
Comment on lines +4 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'function getUTCOffset|new Date\(\)\.getTimezoneOffset|getUTCOffset\(' frontend/src/utils/formatDate.js

Repository: StabilityNexus/Chainvoice

Length of output: 221


🏁 Script executed:

cat frontend/src/utils/formatDate.js

Repository: StabilityNexus/Chainvoice

Length of output: 2112


🏁 Script executed:

rg -n 'getUTCOffset' --include='*.{js,jsx,ts,tsx}'

Repository: StabilityNexus/Chainvoice

Length of output: 510


🏁 Script executed:

rg -n 'getUTCOffset' -t js -t jsx -t ts -t tsx

Repository: StabilityNexus/Chainvoice

Length of output: 96


🏁 Script executed:

rg -n 'getUTCOffset'

Repository: StabilityNexus/Chainvoice

Length of output: 210


Pass the parsed date to getUTCOffset() to ensure the UTC label matches the timestamp's timezone.

getUTCOffset() currently reads the current clock. If a winter timestamp is displayed during DST, the appended UTC label can differ by an hour from the offset used by date.toLocaleString(), creating inconsistency. The parsed date variable is already available in formatDateTime() on line 59—pass it to the function so the label matches the timestamp being shown.

Proposed fix
 /**
- * Returns the user's local UTC offset string, e.g. "UTC+5:30" or "UTC-8:00"
+ * Returns the local UTC offset string for the given date, e.g. "UTC+5:30" or "UTC-8:00"
  */
-function getUTCOffset() {
-  const offset = -new Date().getTimezoneOffset(); // minutes, sign flipped
+function getUTCOffset(date = new Date()) {
+  const offset = -date.getTimezoneOffset(); // minutes, sign flipped
   const sign = offset >= 0 ? "+" : "-";
   const abs = Math.abs(offset);
   const hours = Math.floor(abs / 60);
   const minutes = abs % 60;
   return `UTC${sign}${hours}:${minutes.toString().padStart(2, "0")}`;
@@
-  return `${formatted} (${getUTCOffset()})`;
+  return `${formatted} (${getUTCOffset(date)})`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/utils/formatDate.js` around lines 4 - 10, getUTCOffset currently
reads the current clock instead of the parsed timestamp, causing the UTC label
to mismatch; change getUTCOffset to accept a Date parameter (e.g.,
getUTCOffset(date)) and use date.getTimezoneOffset() inside, then update all
callers—specifically formatDateTime—to pass the parsed variable date into
getUTCOffset so the displayed "UTC±HH:MM" label matches the timestamp's timezone
offset.

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Formats invoice issue/due dates (date only, no time).
* Output: "Feb 20, 2026 (UTC+5:30)"
*
* Why: new Date(isoString).toLocaleDateString() silently shifts the date
* across midnight for users in negative UTC offsets. Rendering the calendar
* date only keeps financial document dates stable across viewers.
*
* @param {string|Date} dateStr
* @returns {string}
+ */
export function formatInvoiceDate(dateStr) {
if (!dateStr) return "N/A";
// Strip time component to avoid UTC→local midnight shift
// "2026-02-20T..." or "2026-02-20" → ["2026","02","20"]
const parts = String(dateStr).split("T")[0].split("-");
if (parts.length === 3) {
const [year, month, day] = parts.map(Number);
const date = new Date(year, month - 1, day); // local date, no TZ conversion
if (isNaN(date.getTime())) return "Invalid date";
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
}
// Fallback for non-ISO formats
const date = new Date(dateStr);
if (isNaN(date.getTime())) return "Invalid date";
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});

Comment on lines +13 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '1,80p' frontend/src/utils/formatDate.js
rg -n 'formatInvoiceDate|formatDateTime' frontend/src/utils/formatDate.js frontend/src/page/SentInvoice.jsx frontend/src/page/BatchPayment.jsx

Repository: StabilityNexus/Chainvoice

Length of output: 3122


🏁 Script executed:

#!/bin/bash
# Get full file to see if formatInvoiceDate exists
wc -l frontend/src/utils/formatDate.js
echo "---"
# Show entire file with line numbers
cat -n frontend/src/utils/formatDate.js
echo "---"
# Check for export statements
rg -n 'export' frontend/src/utils/formatDate.js

Repository: StabilityNexus/Chainvoice

Length of output: 2656


Wrap and export formatInvoiceDate as a function; currently it executes at module scope without a declaration.

Lines 24–44 lack the function wrapper and export statement. The code executes immediately when the module loads, attempting to access undefined dateStr and breaking all imports in BatchPayment.jsx and SentInvoice.jsx that depend on formatInvoiceDate.

🛠️ Proposed fix
 /**
  * Formats invoice issue/due dates (date only, no time).
- * Output: "Feb 20, 2026 (UTC+5:30)"
+ * Output: "Feb 20, 2026"
  *
  * Why: new Date(isoString).toLocaleDateString() silently shifts the date
- * across midnight for users in negative UTC offsets. Showing the offset
- * makes the displayed date unambiguous for financial documents.
+ * across midnight for users in negative UTC offsets. Rendering the calendar
+ * date only keeps financial document dates stable across viewers.
  *
  * `@param` {string|Date} dateStr
  * `@returns` {string}
-*/
-  // Strip time component to avoid UTC→local midnight shift.
+ */
+export function formatInvoiceDate(dateStr) {
+  if (!dateStr) return "N/A";
+  // Strip time component to avoid UTC→local midnight shift.
   // "2026-02-20T..." or "2026-02-20" → ["2026","02","20"]
-  const parts = dateStr.split("T")[0].split("-");
+  const parts = String(dateStr).split("T")[0].split("-");
   if (parts.length === 3) {
     const [year, month, day] = parts.map(Number);
     const date = new Date(year, month - 1, day); // local date, no TZ conversion
     if (isNaN(date.getTime())) return "Invalid date";
     return date.toLocaleDateString("en-US", {
@@
   return date.toLocaleDateString("en-US", {
     year: "numeric",
     month: "short",
     day: "numeric",
   });
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/utils/formatDate.js` around lines 13 - 45, Wrap the existing
date-parsing logic in a function named formatInvoiceDate(dateStr) (so it no
longer runs at module load) and export it (export function formatInvoiceDate or
export default formatInvoiceDate); inside the function accept either a string or
a Date (if a Date is passed, convert to an ISO-like yyyy-mm-dd string or extract
year/month/day via getFullYear/getMonth/getDate) then perform the current
ISO-splitting, local Date construction, validation (isNaN check) and
toLocaleDateString formatting, and keep the fallback branch for non-ISO strings
— this ensures imports in BatchPayment.jsx and SentInvoice.jsx don't break and
preserves existing behavior.

/**
* Formats a full timestamp (date + time) for table rows.
* * Output: "Feb 20, 2026"
*
* @param {string|Date} dateStr
* @returns {string}
*/
export function formatDateTime(dateStr) {
if (!dateStr) return "N/A";
const date = new Date(dateStr);
if (isNaN(date.getTime())) return "Invalid date";
const formatted = date.toLocaleString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
return `${formatted} (${getUTCOffset()})`;
}
}
14 changes: 4 additions & 10 deletions frontend/src/utils/generateInvoicePDF.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import jsPDF from "jspdf";
import { ethers } from "ethers";
import { getWagmiChainName, getWagmiChainInfo } from "./wagmiChainHelpers";
import { formatInvoiceDate } from "./formatDate";


/**
* Load logo image with multiple fallback methods
Expand Down Expand Up @@ -369,16 +371,8 @@ export const generateInvoicePDF = async (invoice, fee = 0) => {
pdf.setTextColor(...darkGray);
pdf.setFontSize(9);
pdf.setFont("helvetica", "normal");
const issueDate = new Date(invoice.issueDate).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
const dueDate = new Date(invoice.dueDate).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
const issueDate = formatInvoiceDate(invoice.issueDate);
const dueDate = formatInvoiceDate(invoice.dueDate);
pdf.text(`Issued: ${issueDate}`, 25, yPos + 5.5);
pdf.text(`Due: ${dueDate}`, 160, yPos + 5.5);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Expand Down