diff --git a/frontend/src/components/InvoicePreview.jsx b/frontend/src/components/InvoicePreview.jsx
index 3e7e895d..62b1a9f6 100644
--- a/frontend/src/components/InvoicePreview.jsx
+++ b/frontend/src/components/InvoicePreview.jsx
@@ -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,
@@ -293,11 +294,7 @@ const InvoicePreview = ({
Issued:
- {new Date(invoice.issueDate).toLocaleDateString("en-US", {
- year: "numeric",
- month: "short",
- day: "numeric",
- })}
+ {formatInvoiceDate(invoice.issueDate)}
@@ -305,11 +302,7 @@ const InvoicePreview = ({
Due:
- {new Date(invoice.dueDate).toLocaleDateString("en-US", {
- year: "numeric",
- month: "short",
- day: "numeric",
- })}
+ {formatInvoiceDate(invoice.dueDate)}
diff --git a/frontend/src/page/BatchPayment.jsx b/frontend/src/page/BatchPayment.jsx
index 6982df09..8326dc19 100644
--- a/frontend/src/page/BatchPayment.jsx
+++ b/frontend/src/page/BatchPayment.jsx
@@ -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,
@@ -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
);
@@ -1186,7 +1182,7 @@ function BatchPayment() {
)}
- {formatDate(invoice.issueDate)}
+ {formatInvoiceDate(invoice.issueDate)}
|
@@ -1482,16 +1478,10 @@ function BatchPayment() {
- Issued:{" "}
- {new Date(
- drawerState.selectedInvoice.issueDate
- ).toLocaleDateString()}
+ Issued: {formatInvoiceDate(drawerState.selectedInvoice.issueDate)}
- Due:{" "}
- {new Date(
- drawerState.selectedInvoice.dueDate
- ).toLocaleDateString()}
+ Due: {formatInvoiceDate(drawerState.selectedInvoice.dueDate)}
diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx
index 1bac242f..f7b9a720 100644
--- a/frontend/src/page/ReceivedInvoice.jsx
+++ b/frontend/src/page/ReceivedInvoice.jsx
@@ -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 },
@@ -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
);
@@ -1404,7 +1400,7 @@ function ReceivedInvoice() {
- {formatDate(invoice.issueDate)}
+ {formatDateTime(invoice.issueDate)}
@@ -1780,16 +1776,10 @@ function ReceivedInvoice() {
- Issued:{" "}
- {new Date(
- drawerState.selectedInvoice.issueDate
- ).toLocaleDateString()}
+ Issued: {formatInvoiceDate(drawerState.selectedInvoice.issueDate)}
- Due:{" "}
- {new Date(
- drawerState.selectedInvoice.dueDate
- ).toLocaleDateString()}
+ Due: {formatInvoiceDate(drawerState.selectedInvoice.dueDate)}
diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx
index b953713f..a104d485 100644
--- a/frontend/src/page/SentInvoice.jsx
+++ b/frontend/src/page/SentInvoice.jsx
@@ -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 },
@@ -417,11 +418,6 @@ function SentInvoice() {
)}`;
};
- const formatDate = (issueDate) => {
- const date = new Date(issueDate);
- return date.toLocaleString();
- };
-
return (
<>
@@ -613,7 +609,7 @@ function SentInvoice() {
{/* Date Column */}
- {formatDate(invoice.issueDate)}
+ {formatInvoiceDate(invoice.issueDate)}
@@ -922,16 +918,10 @@ function SentInvoice() {
- Issued:{" "}
- {new Date(
- drawerState.selectedInvoice.issueDate
- ).toLocaleDateString()}
+ Issued: {formatInvoiceDate(drawerState.selectedInvoice.issueDate)}
- Due:{" "}
- {new Date(
- drawerState.selectedInvoice.dueDate
- ).toLocaleDateString()}
+ Due: {formatInvoiceDate(drawerState.selectedInvoice.dueDate)}
diff --git a/frontend/src/utils/formatDate.js b/frontend/src/utils/formatDate.js
new file mode 100644
index 00000000..b4ce1cc2
--- /dev/null
+++ b/frontend/src/utils/formatDate.js
@@ -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")}`;
+}
+
+/**
+ * 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",
+ });
+
+ /**
+ * 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()})`;
+ }
+}
diff --git a/frontend/src/utils/generateInvoicePDF.js b/frontend/src/utils/generateInvoicePDF.js
index 52a0a14d..c32b78a0 100644
--- a/frontend/src/utils/generateInvoicePDF.js
+++ b/frontend/src/utils/generateInvoicePDF.js
@@ -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
@@ -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);
|