v3.0.0 — invoicing behind a login. A shared ledger every signed-in user can read, each invoice recording who raised it, with a live preview that is the printed document.
v1–v2 were a Create React App single-pager that kept invoices in localStorage.
v3 is the same job rebuilt on Backflip
(backflip.dev-geddy.com) — a platform foundation
that ships Postgres, real accounts and roles, an admin shell, and a shared UI system
on day one.
So this repo is two things at once: a working invoicing app, and a worked example of how quickly a new platform, internal business tool or website gets built on Backflip without trading away quality — auth, roles, migrations, transactional email, integrations, tests and deploys are already there, so the feature work is the only work left.
Stack: Next.js 16 · React 19 · Tailwind v4 · shadcn/ui · Drizzle + Postgres · Auth.js · Turborepo · yarn 4.
- Shared ledger —
/backflip/invoicing/invoices. Per-series totals and a cumulative-sales chart for the tax year, then every invoice. Each one opens on its own page; the creator (or an owner/admin) may edit, lock or delete it. - Editor + live preview — provider and customer as cards opening a four-column detail dialog, line items whose qty/rate/total recompute each other, hyphenated series numbering (
RIE-0010), VAT charged only when the provider is registered for it. - Print & PDF — the preview is the document. Print it from the browser, or download a server-rendered A4 PDF; either way the file is named after the invoice (
2026_08_21 - RIE-0010 - EUR1031.25 VAT incl. - UAB Baltic Grid.pdf). - Customers —
/backflip/invoicing/customers. An address book that feeds the invoice form's prefill; companies invoiced but not saved are offered for one-click adoption. - Series & currency —
/backflip/invoicing/settings. Each series owns its numbering prefix, default currency and branding, with a platform brand as fallback. Invoices snapshot all three, so editing a series never rewrites an issued invoice.
Contract: docs/contracts/invoice.md · notes: docs/notes/invoice.md.
The v2 CRA/MUI/localStorage app is kept for reference in .legacy-ref-project/ — reference only, not built or deployed.
Self-hosted. You run this yourself and supply your own secrets. The values in
.env.exampleare local-dev defaults only — generate real secrets before deploying anywhere (see Security).
Set up once — clone, provision a droplet, first deploy — then every feature runs the same loop: describe it, let Claude Code build to spec, review the PR, deploy.
- Docker Desktop (running) — for Postgres
- Node ≥ 20 with corepack (pins
yarn@4.17.1— always runcorepack yarn …)
1. Create env files (first time only). Copy the templates:
cp .env.example .env
cp .env.init.example .env.init # one-off owner seed — see step 2Create .env.local (runtime Auth.js secrets) with:
AUTH_SECRET=replace-with-openssl-rand-base64-33
AUTH_TRUST_HOST=true
Generate a real secret with openssl rand -base64 33. Edit .env.init with your
ADMIN_EMAIL / ADMIN_PASSWORD — it's read only by init-owner, never by the app,
and can be deleted once the owner is seeded.
2. Install, set up the database, run:
corepack yarn install
docker compose up -d # start Postgres (react-invoice-db) only
corepack yarn db:migrate # create tables
corepack yarn init-owner # seed admin from .env.init
corepack yarn dev # app → http://localhost:3080- Sign in at http://localhost:3080/backflip/login with your
ADMIN_EMAIL/ADMIN_PASSWORD. - After editing env:
- changed
ADMIN_*in.env.init→ reruncorepack yarn init-owner - changed
AUTH_*(incl. Google) in.env.local→ restartyarn dev(env loads at startup)
- changed
- Google login is optional — add
AUTH_GOOGLE_ID/AUTH_GOOGLE_SECRET(redirect URIhttp://localhost:3080/api/auth/callback/google); works only for already-registered emails.
corepack yarn workspace web test # unit (Vitest) — fast, no database needed
corepack yarn workspace web test:e2e # e2e (Playwright) — needs `docker compose up -d`E2e boots its own app on port 3180 against a dedicated react_invoice_test database —
your dev server and dev data are untouched. First run on a machine also needs the
browser once: corepack yarn workspace web exec playwright install chromium.
Optional: corepack yarn workspace web test:e2e:screenshots captures key pages
into .screenshots/ (gitignored).
The owner seed is a one-off kickoff step. Run it a single time against the prod database, then delete the credentials file:
cp .env.init.example .env.init # then edit: real ADMIN_EMAIL + a strong ADMIN_PASSWORD
corepack yarn init-owner # creates the owner row — idempotent, safe to re-run
rm .env.init # remove the secret once you can sign inDeleting .env.init is all that's needed — the ADMIN_PASSWORD lives only there.
The seed script isn't imported by the app, isn't in the build, and does nothing
without .env.init (it throws on a missing ADMIN_EMAIL), so it's harmless to leave
in place. Re-seed anytime by recreating .env.init and rerunning init-owner.
Optional cleanup: to drop the tooling entirely,
git rm .env.init.example packages/db/src/seed/owner.ts, remove theinit-ownerentry frompackage.json
packages/db/package.json, and prune theL2-DB-04/13/15doc lines. Pure hygiene — restore from git history to seed another owner later.
- Ports: app
3080, Postgres5545(changePOSTGRES_PORTin.envif it clashes). - Secrets: set a real
ENCRYPTION_KEYin.env(openssl rand -base64 32) — it encrypts stored secrets (AI provider keys). - DB changes: edit
packages/db/src/schema.ts→corepack yarn db:generate→db:migrate. - Admin dashboard UI is built from shadcn blocks — browse and lift components/layouts from https://ui.shadcn.com/blocks (this project uses
login-03,dashboard-01,sidebar-08). - Full Docker (app + db):
docker compose --profile web up -d --build→ migrations run, app on3081. Owner seed:docker compose --profile seed run --rm db-seed. - More commands + conventions:
.claude/skills/dev-workflow.
Deploy to a DigitalOcean droplet — from your machine, GitHub Actions, or Drone CI. See devops.md.
Before deploying, generate real secrets — never reuse the .env.example defaults:
AUTH_SECRET→openssl rand -base64 33ENCRYPTION_KEY→openssl rand -base64 32- a strong
ADMIN_PASSWORDand unique database credentials.
To report a vulnerability, see SECURITY.md.
MIT © dev-geddy

