git-subtree-dir: apps/web git-subtree-mainline:068a51b46bgit-subtree-split:1e7174fa60
beenvoice workspace
Local development layout for the beenvoice product: a freelancer/small-business invoicing platform with a Next.js web app and an Expo mobile companion.
beenvoice-meta/
├── beenvoice-web/ # Web API + dashboard (Next.js 16, tRPC, PostgreSQL)
├── beenvoice-app/ # Mobile app (Expo, React Native, dev client)
└── beenvoice.icon/ # iOS app icon asset (icon composer)
Each subdirectory is its own git repository. This folder is a convenience layout for working on both clients against one API.
| Repo | Remote |
|---|---|
| Web | git.soconnor.dev/soconnor/beenvoice-web |
| Mobile | git.soconnor.dev/soconnor/beenvoice-app |
Quick start (full stack)
# 1. API + database
cd beenvoice-web
cp .env.example .env.local # edit DATABASE_URL, AUTH_SECRET, etc.
docker compose -f docker-compose.dev.yml up -d
bun install && bun run db:push
bun run dev # http://localhost:3000
# 2. Mobile (simulator)
cd ../beenvoice-app
cp .env.example .env # EXPO_PUBLIC_API_URL=http://localhost:3000
bun install
bun run ios # Metro on :8082, native dev build
Physical iPhone: set EXPO_PUBLIC_API_URL to your Mac's LAN IP; ensure beenvoice-web has BETTER_AUTH_URL matching a host the device can reach (trusted origins are derived from BETTER_AUTH_URL / NEXT_PUBLIC_APP_URL).
Production deploy (Docker)
From beenvoice-web with .env configured (see beenvoice-web/README.md):
cd beenvoice-web
git pull
./scripts/docker-deploy.sh # rebuild app image + restart (not docker-compose.dev.yml)
Plain docker compose up -d without --build reuses the existing local image and will not pick up code from git pull.
How the pieces connect
| Layer | Repo | Transport | Auth |
|---|---|---|---|
| Web UI | beenvoice-web |
tRPC /api/trpc (cookies) |
better-auth session |
| Mobile UI | beenvoice-app |
tRPC /api/trpc (cookie header) |
better-auth + @better-auth/expo → SecureStore |
| Automation | beenvoice-web MCP /api/mcp |
JSON-RPC | API key (bv_…) only |
| Public invoices | beenvoice-web /i/[token] |
HTTP | unauthenticated token |
Mobile imports tRPC router types from beenvoice-web/src/server/api/root via tsconfig path mapping — keep API changes type-checked in both repos.
Documentation map
| Doc | Contents |
|---|---|
| beenvoice-web/README.md | Web setup, Docker deploy, scripts |
| beenvoice-web/docs/ARCHITECTURE.md | Server architecture (dense) |
| beenvoice-app/README.md | Mobile setup, troubleshooting |
| beenvoice-app/docs/ARCHITECTURE.md | Mobile architecture (dense) |
| beenvoice-web/docs/ | UI guides (forms, tables, breadcrumbs, email) |
Shared domain concepts
- Clients — billable contacts with optional default hourly rate
- Businesses — sender profiles (logo, address, Resend email config)
- Invoices — draft → sent → paid; line items; PDF + email; public share token
- Time entries — clock in/out; one running timer per user; auto-attach to open invoice
- Recurring invoices, expenses, payments, templates — full web support; mobile covers a core CRUD subset
Environment cheat sheet
| Variable | Where | Purpose |
|---|---|---|
DATABASE_URL |
beenvoice-web | PostgreSQL |
AUTH_SECRET, BETTER_AUTH_URL |
beenvoice-web | better-auth |
EXPO_PUBLIC_API_URL |
beenvoice-app | API base for mobile |
DISABLE_SIGNUPS |
beenvoice-web | true blocks new registrations |
RESEND_* |
beenvoice-web | Invoice / reset email |
Ports
| Service | Default port |
|---|---|
| Next.js (beenvoice-web) | 3000 |
| Postgres (dev compose) | 5432 |
| Metro (beenvoice-app) | 8082 (intentionally not 8081) |