Skip to content

fix(billing): real invoice download with error handling#211

Merged
Jagadeeshftw merged 2 commits into
Grainlify:mainfrom
OTimileyin:fix/invoices-download
Jun 23, 2026
Merged

fix(billing): real invoice download with error handling#211
Jagadeeshftw merged 2 commits into
Grainlify:mainfrom
OTimileyin:fix/invoices-download

Conversation

@OTimileyin

Copy link
Copy Markdown
Contributor

Closes #191
Title:
fix(billing): real invoice download with error handling

Body:

Summary

  • Replaces the stub link.href = '#' with a real fetch â�� Blob â�� object URL download
  • Adds per-row loading and error states so failures are surfaced without blocking other rows
  • Wraps the invoice table in overflow-x-auto / min-w-[640px] to fix mobile overflow

How the download works

handleDownloadInvoice calls the new downloadInvoice(id) function in client.ts,
which issues an authenticated GET /billing/invoices/:id/download and returns a Blob.
The component then:

  1. Creates a temporary object URL via URL.createObjectURL(blob)
  2. Attaches it to a transient <a> element and calls .click()
  3. Immediately calls URL.revokeObjectURL(url) to release the Blob from memory

The download filename is derived from invoice.invoiceNumber wi
non-[a-zA-Z0-9\-_] characters replaced, so it is never influenced by an
unsanitized server-provided value.

Loading & error handling

  • downloadingId: string | null â�� disables and shows a spinner
    fetched; all other rows stay interactive
  • downloadErrors: Record<string, string> â�� shows a `role="aler
    below the failing row; cleared on the next successful attempt

Responsive fix

The six-column grid is now wrapped in overflow-x-auto with min-w-[640px] on the
inner container � the table scrolls horizontally on narrow viewp
overflowing.

Tests & coverage

18 tests across 6 describe blocks:

  • Rendering â�� empty state, all three status variants (paid / pending / overdue)
  • Download success â�� correct invoice id passed, object URL c
  • Download error â�� Error message shown, non-Error fallback, per-row isolation,
    no object URL calls on failure
  • Loading state â�� button disabled + aria-label updated mid-flight, only the active
    row affected
  • Responsive layout â�� overflow-x-auto wrapper and min-w- constraint verified
  • Dark theme â�� all theme ternary branches covered

Coverage for InvoicesTab.tsx: **100% statements / branches / f

Security note

  • Object URLs are revoked immediately after the click â�� no Blob
  • Filenames are sanitized with /[^a-zA-Z0-9\-_]/g â�� no raw server string reaches
    link.download

@Jagadeeshftw Jagadeeshftw merged commit d8f2752 into Grainlify:main Jun 23, 2026
@Jagadeeshftw

Copy link
Copy Markdown
Contributor

real invoice download with proper auth and error handling is a solid upgrade over a stubbed button. rebased on latest main and combined it cleanly with the terms api additions in client.ts. thanks!

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.

Make InvoicesTab download produce a real file and handle download errors

3 participants