The Problem

Freelancers and small firms need an offline‑first invoicing tool that stores data locally, offers PDF export, and runs on Windows, macOS and Linux without a cloud lock‑in. Existing SaaS solutions force subscription fees and expose sensitive financial data.

What This Does

invoice-builder is an Electron desktop application that bundles a React renderer with an Express‑style IPC layer. Core UI lives under src/renderer/, while database access and native helpers live under src/main/.

  • UI components (e.g. src/renderer/pages/invoices/Form/*.tsx) assemble invoice data defined in src/renderer/shared/types/invoice.ts.
  • The SQLite database is opened in src/main/database.ts and accessed through utility wrappers in src/main/utils/dbFuntions.ts.
  • PDF generation is triggered from the renderer via the hook src/renderer/shared/hooks/useExportPdf.tsx, which ultimately calls the main process IPC src/main/ipc/invoices.ts.

How It Is Wired

  1. Application start – src/main/main.ts creates the Electron BrowserWindow and loads index.html.
  2. Renderer bootstrap – src/renderer/main.tsx mounts src/renderer/app/App.tsx (entry point # App).
  3. State store – src/renderer/state/configureStore.ts builds the Redux store; src/renderer/state/pageSlice.ts adds page navigation state. These two files form the only import cycle in the graph.
  4. Data fetch – UI pages call the custom hook src/renderer/shared/hooks/useAsyncAction.ts (the most‑connected module, imported by 42 files). This hook dispatches async IPC calls to the main process (src/main/ipc/*).
  5. Database work – In the main process, src/main/utils/dbFuntions.ts provides runAsync, runDb, getFirstRow, etc. All DB reads/writes funnel through these six functions; they are invoked from the IPC handlers (e.g. src/main/ipc/invoices.ts).
  6. PDF export – src/renderer/shared/hooks/useExportPdf.tsx gathers invoice data, calls getAttachmentsUrl, getLogoUrl, etc., then sends a request to src/main/ipc/importExport.ts which writes the PDF file (the only external‑IO function).
  7. Key blast‑radius modules – src/renderer/shared/types/response.ts is imported by 60 modules (hub). src/renderer/shared/types/invoice.ts is imported by 51 modules (hub). * src/renderer/pages/invoices/Form/index.tsx is 620 lines and called only from the router, but many UI components depend on it, making it a change‑impact hotspot.

The internal call graph shows useAsyncAction → runAsync (41 calls) as the longest cross‑process path, indicating that most side‑effects (DB writes, file writes) travel through this pair.

How To Use It

# Clone the repo (use the exact URL as requested)
git clone https://github.com/moses-y/invoice-builder
cd invoice-builder

# Install exact lock‑file versions
npm ci

# Build the renderer (Vite) and package the Electron app
npm run build          # defined in package.json → Vite + Electron builder
npm run electron:build # uses electron-builder.yml to produce .exe/.dmg/.deb

No environment files are required for a default run; the app creates a local SQLite file (src/main/database.ts) on first launch.

To start the development version:

npm run dev   # launches Electron with hot‑reloaded Vite renderer

Real‑World Use

A freelance graphic designer installs the packaged .dmg, opens the app, creates a client record, adds line‑items, and clicks “Export PDF”. The UI calls useExportPdf, which pulls the invoice JSON from the Redux store, asks the main process to render the PDF, and writes Invoice‑2024‑03‑01.pdf to the user‑chosen folder—all offline and without any network request.

Code Health & Issues

  • High – Add a test suite – 282 source files, only 2 test files.
  • Medium – Use npm ci in CI – workflow runs npm install despite a lockfile.
  • Medium – Dependency vulnerability scan – no scan step in .github/workflows.
  • Medium – Checkout without persist-credentials:false – token persists across steps.
  • Low – No job timeout – workflow jobs lack timeout‑minutes.

Additional static findings:

  • High – Import cycle (src/renderer/state/configureStore.ts ↔ src/renderer/state/pageSlice.ts).
  • High – Hub modules (src/renderer/shared/types/response.ts, src/renderer/shared/types/invoice.ts).
  • High – Deep nesting (max indent depth 6 in several UI components).
  • High – Duplicated enums (filterType, invoiceStatus duplicated in main and renderer).
  • Medium – Oversized file (src/renderer/pages/invoices/Form/index.tsx, 620 lines).

Dependencies are several major versions behind (e.g., Electron ^39 → current 43, MUI ^7 → 9). Updating them will reduce security exposure but may require UI refactoring.

The Bottom Line

invoice-builder delivers a functional offline invoicing app with a clear Electron‑React architecture, but the codebase suffers from a few structural hotspots (import cycle, large hub files, duplicated enums) and an almost nonexistent test suite. It is suitable for small‑business teams comfortable with TypeScript and willing to invest in modest refactoring and testing before extending the product.