soconnorandCursor 6b73c32c25 Add bulk invoice import with templates and refresh import UX
Move invoice import configuration into settings, redesign the import flow with shared components and sample templates, document the demo account in README, and polish upload and button styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 12:39:46 -04:00
2026-06-26 00:12:09 -04:00
2026-06-26 00:12:09 -04:00

beenvoice Logo

beenvoice-web

Web application and API for beenvoice — invoicing for freelancers and small businesses. Includes the Next.js dashboard, tRPC API, better-auth, PostgreSQL persistence, PDF/email delivery, time tracking, and an MCP automation endpoint.

Repository: git.soconnor.dev/soconnor/beenvoice-web
Architecture: docs/ARCHITECTURE.md
Mobile companion: beenvoice-app (separate repo; often checked out beside this one in a workspace)

Stack

Layer Technology
App Next.js 16 App Router, React 19
API tRPC 11 + SuperJSON
Database PostgreSQL 17, Drizzle ORM
Auth better-auth (email/password, optional Authentik OIDC, Expo mobile)
UI shadcn/ui, Tailwind CSS v4
Email / PDF Resend, @react-pdf/renderer
Runtime Bun

Features

  • Clients, businesses, invoices (line items, tax, status workflow)
  • Time clock with one running timer per user; clock-out can append invoice lines
  • Expenses, payments, recurring invoices, invoice templates
  • PDF export and email delivery (Resend)
  • Public invoice links (/i/[token])
  • CSV import, reports, platform branding / admin settings
  • MCP API (/api/mcp) for automation via API keys (bv_…)
  • Optional Authentik OIDC SSO

Prerequisites

  • Bun 1.x
  • Docker & Docker Compose (for PostgreSQL locally or full-stack deploy)
  • Git

Local development

1. Clone and install

git clone https://git.soconnor.dev/soconnor/beenvoice-web.git
cd beenvoice-web
bun install

2. Environment

cp .env.example .env.local

Edit .env.local for local dev. Minimum:

DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
DB_DISABLE_SSL=true
AUTH_SECRET=your-dev-secret          # openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000

Email and SSO are optional for local work — leave RESEND_* and AUTHENTIK_* blank unless you need them.

3. Database

Start Postgres (dev compose exposes port 5432):

docker compose -f docker-compose.dev.yml up -d

After a fresh volume (docker compose down -v), Postgres starts empty — you must apply schema before registering or signing in.

Apply schema (pick one):

bun run db:push      # fast iteration during development
# bun run db:migrate # same migrations the Docker image runs in production

Demo account. For App Store review and local testing, bun run db:migrate applies 0014_seed_demo_account.sql, which creates a pre-populated user (db:push does not). Sign in at /auth/login:

  • Email: demo@example.com
  • Password: demo123

The account includes a sample business, clients, and invoices (draft, sent, and paid).

4. Run

bun run dev

Open http://localhost:3000, register at /auth/register, or sign in with the demo account above.

Docker deployment (app + database)

The production compose file runs the Next.js app and PostgreSQL.

Container startup runs bun migrate.ts && bun run start (see Dockerfile). Drizzle only applies pending migrations — safe to run on every restart; already-applied migrations are skipped.

The Docker build runs next build on Node 22 (Bun can crash on Linux arm64 during the page-data worker phase). The runtime image still uses Bun for migrations and next start. docker-compose.yml does not set container memory or CPU limits — containers can use whatever the Docker host provides.

1. Configure

cp .env.example .env

Set at least:

AUTH_SECRET=<openssl rand -base64 32>
BETTER_AUTH_URL=https://your-public-hostname
NEXT_PUBLIC_APP_URL=https://your-public-hostname

BETTER_AUTH_URL and NEXT_PUBLIC_APP_URL must match the URL users actually use in the browser (scheme + host + port). If they point at localhost but you access the app via another hostname, sign-up and sign-in will fail (often with a vague "REQUIRED" toast).

NEXT_PUBLIC_* values are embedded at image build time. Rebuild after changing NEXT_PUBLIC_APP_URL, white-label defaults, or NEXT_PUBLIC_AUTHENTIK_ENABLED:

docker compose build --no-cache app

BETTER_AUTH_URL and AUTH_SECRET are read at container runtime from .env — you can change them without rebuilding, then restart the app container.

2. First start (or after code changes)

docker compose up -d --build

--build is important. A plain docker compose up -d reuses the existing image and does not pick up new code from git pull.

App listens on ${WEB_PORT:-${PORT:-3000}} on the host (container port is always 3000). Postgres stays on the internal compose network.

3. Updating an existing deploy

git pull
docker compose up -d --build   # rebuild image, restart app, run any new migrations
Command New code? Migrations run?
git pull only No No
docker compose up -d (no --build) No — old image Only if the app container restarts (same image)
docker compose up -d --build Yes Yes — on app container start
docker compose restart app No Yes — migrate runs again (no-op if up to date)

To verify migration files match the journal before deploy: bun run db:verify-journal.

4. Sign-ups

Registration is enabled by default. To block new email/password accounts:

DISABLE_SIGNUPS=true

Use the literal strings true or false (or omit the variable). Do not rely on bare boolean coercion from shell/compose — the app parses these explicitly.

5. Optional services

Variable Purpose
RESEND_API_KEY, RESEND_DOMAIN Invoice and password-reset email
AUTHENTIK_ISSUER, AUTHENTIK_CLIENT_ID, AUTHENTIK_CLIENT_SECRET OIDC SSO (also set NEXT_PUBLIC_AUTHENTIK_ENABLED=true and rebuild)
CRON_SECRET Protects /api/cron/generate-recurring
DISABLE_SIGNUPS=true Block new registrations

Project structure

beenvoice-web/
├── src/app/              # Routes (dashboard, auth, /api/*)
├── src/server/api/       # tRPC routers
├── src/server/db/        # Drizzle schema, pool, migrate.ts
├── src/components/       # UI (ui/, forms/, layout/, branding/)
├── src/lib/              # auth, PDF, email, branding helpers
├── drizzle/              # SQL migrations
├── Dockerfile            # Production image (migrate + next start)
├── docker-compose.yml    # App + Postgres (deploy)
├── docker-compose.dev.yml # Postgres only (local dev)
└── docs/                 # Architecture and UI guides

See docs/ARCHITECTURE.md for routers, schema, auth flows, and MCP.

Scripts

# App
bun run dev              # next dev --turbo
bun run build            # production build
bun run start            # next start
bun run check            # eslint + tsc

# Database
bun run db:push          # push schema (local dev)
bun run db:migrate       # run drizzle migrations
bun run db:generate      # generate new migration SQL
bun run db:studio        # Drizzle Studio

# Formatting
bun run lint
bun run lint:fix
bun run format:write
bun run typecheck

# Docker helpers (Postgres only — uses Colima on macOS)
bun run docker:up        # colima start + docker-compose.dev.yml up -d
bun run docker:down      # stop dev Postgres + colima

Full-stack deploy uses docker compose up (see Docker deployment), not bun run docker:up.

API surface

Endpoint Auth Purpose
/api/trpc Session cookie or API key Primary API (web + mobile)
/api/auth/* Varies better-auth + custom register/reset REST
/api/mcp API key only JSON-RPC automation tools
/i/[token] Public token Client invoice view
/api/i/[token]/pdf Public token Invoice PDF download

Business logic lives in src/server/api/routers/ with Zod validation.

Customization

  • Runtime branding: Dashboard → Administration (platform settings)
  • Build-time defaults: NEXT_PUBLIC_BRAND_* in .env (rebuild Docker image to apply)
  • Theme / fonts: src/styles/globals.css, appearance settings in the app
  • Logo component: src/components/branding/logo.tsx

Documentation

Doc Contents
docs/ARCHITECTURE.md Stack, routers, schema, auth, Docker, MCP
docs/README.md Index of UI and product guides
AGENTS.md Conventions for AI-assisted development

License

MIT — see LICENSE.

S
Description
beenvoice web app (Next.js)
Readme
4.1 MiB
Languages
TypeScript 98.6%
JavaScript 0.5%
Shell 0.4%
CSS 0.4%
Dockerfile 0.1%