Skip to content
Merged
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
8 changes: 7 additions & 1 deletion src/app/(app)/invoices/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ export default async function InvoicePage({ params }: InvoicePageProps) {
</Button>
</form>
<DeleteDraftDialog action={deleteDraftInvoiceAction} invoiceId={invoice.id} />
<Button asChild className="text-[13px]" variant="outline">
<a href={`/api/invoices/${invoice.id}/pdf`}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Give each draft download an identifiable filename

Every draft maps its missing invoice number to DRAFT, and the PDF endpoint derives Content-Disposition solely from that value, so all links enabled here download as DRAFT.pdf. When a user exports multiple drafts, clients may overwrite the earlier file or create indistinguishable names such as DRAFT (1).pdf, increasing the chance of sending the wrong invoice; include a stable draft-specific identifier in the filename while retaining the DRAFT label inside the document.

Useful? React with 👍 / 👎.

<Download aria-hidden="true" className="size-[14px]" />
Download PDF
</a>
</Button>
<IssueInvoiceDialog action={issueInvoiceAction} invoiceId={invoice.id} />
</>
) : (
Expand Down Expand Up @@ -156,7 +162,7 @@ export default async function InvoicePage({ params }: InvoicePageProps) {
<h2 className="text-[14px] font-medium text-foreground">Invoice preview</h2>
<p className="mt-0.5 text-[12px] text-subtle-foreground">The downloaded PDF uses this same document.</p>
</div>
{invoice.lifecycle === "issued" ? (
{invoice.lifecycle !== "void" ? (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid presenting drafts with an issued date

When a draft has no issue_date, enabling this download produces a PDF whose date row says Issued <created_at>, because buildInvoiceDocumentData substitutes the creation date for issuedAt and the shared document view always labels that field “Issued.” Thus an invoice that the details page reports as “Not issued” becomes an externally shareable document claiming an issuance date; draft PDFs should omit that row or label it as a draft/creation date.

Useful? React with 👍 / 👎.

<Button asChild className="rounded-full" size="icon-sm" variant="ghost">
<a aria-label="Download invoice PDF" href={`/api/invoices/${invoice.id}/pdf`}>
<Download aria-hidden="true" className="size-[14px]" />
Expand Down
6 changes: 0 additions & 6 deletions src/app/api/invoices/[id]/pdf/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ export async function GET(_request: Request, { params }: PdfRouteContext) {
if (!record) {
return Response.json({ error: "Invoice not found" }, { status: 404 });
}
if (record.lifecycle === "draft") {
return Response.json(
{ error: "Issue this invoice before downloading its PDF" },
{ status: 409 },
);
}
const { data } = record;

const [{ renderToBuffer }, { InvoicePdfDocument }] = await Promise.all([
Expand Down