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 insrc/renderer/shared/types/invoice.ts. - The SQLite database is opened in
src/main/database.tsand accessed through utility wrappers insrc/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 IPCsrc/main/ipc/invoices.ts.
How It Is Wired
- Application start –
src/main/main.tscreates the ElectronBrowserWindowand loadsindex.html. - Renderer bootstrap –
src/renderer/main.tsxmountssrc/renderer/app/App.tsx(entry point # App). - State store –
src/renderer/state/configureStore.tsbuilds the Redux store;src/renderer/state/pageSlice.tsadds page navigation state. These two files form the only import cycle in the graph. - 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/*). - Database work – In the main process,
src/main/utils/dbFuntions.tsprovidesrunAsync,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). - PDF export –
src/renderer/shared/hooks/useExportPdf.tsxgathers invoice data, callsgetAttachmentsUrl,getLogoUrl, etc., then sends a request tosrc/main/ipc/importExport.tswhich writes the PDF file (the only external‑IO function). - Key blast‑radius modules –
src/renderer/shared/types/response.tsis imported by 60 modules (hub).src/renderer/shared/types/invoice.tsis imported by 51 modules (hub). *src/renderer/pages/invoices/Form/index.tsxis 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 ciin CI – workflow runsnpm installdespite 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,invoiceStatusduplicated 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.