183 Commits

Author SHA1 Message Date
soconnor 480c50981d Unify entities navigation, redesign time clock, and add invoice PDF preview.
Combine clients and businesses under entities, polish the web time clock,
and show live invoice PDF preview with tighter line-item editing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 01:08:23 -04:00
soconnor 0b7ffac4e7 Add migration 0016 to fix missing sendReminderAt column.
0015 was already marked applied with the wrong column name, so redeploys
skipped it; 0016 drops send_reminder_at and adds sendReminderAt.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:22:11 -04:00
soconnor 9b72afdf69 Fix sendReminderAt migration column name and register in journal.
Use camelCase sendReminderAt to match existing invoice columns; the
snake_case name caused dashboard queries to fail after deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:16:46 -04:00
soconnor 9e7177a869 fix: journal update 2026-06-22 16:12:09 -04:00
soconnor 1928084acb Add draft-only invoicing rules, send reminders, and time clock billing.
Restrict line item edits to draft invoices, auto-create drafts on clock-out,
and add sendReminderAt scheduling with dashboard due reminders.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-22 16:06:11 -04:00
soconnor 4cd8ad3c4c Redesign legal pages with readable paragraph layout and updated contact info.
Privacy Policy and Terms now share a document layout with table of contents,
plain-paragraph copy, and beenvoice.soconnor.dev contact details for App Store review.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 02:44:43 -04:00
soconnor 5019a7597d Expose public auth capabilities endpoint for mobile SSO detection.
Returns whether Authentik and signups are enabled so the app can show the right
sign-in options per instance at runtime.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 02:27:31 -04:00
soconnor 40020b78f8 fix: auth lock privacy 2026-06-18 01:45:18 -04:00
soconnor 69da2bf71d Add shared legal pages and wire Privacy Policy and Terms across the app.
Extract privacy and terms content into reusable components, replace auth modals with links to /privacy and /terms, add settings legal section, and remove duplicate legal-modal markup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 01:33:34 -04:00
soconnor b7380f4348 Add demo account seed migration for App Store review.
Seeds demo@example.com with sample clients, business, and invoices via the Drizzle journal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 23:14:45 -04:00
soconnor 5c28b33e9f Unify time tracking on server-backed entries with updateRunning.
Consolidates dashboard and invoice timers onto shared time-entry APIs so clock-in state, invoice linking, and clock-out flow stay consistent across surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 22:36:39 -04:00
soconnor 81a0ce33a4 Add better-auth Expo plugin for mobile app authentication.
Enable session-based auth from the Expo companion app via SecureStore cookies and trusted deep-link origins.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 15:56:53 -04:00
soconnor 190fbd433b fix: sort chronologically 2026-06-12 00:11:00 -04:00
soconnor 94e0a18bf5 fix: missing publicTokenExpiresAt 2026-06-11 12:19:13 -04:00
soconnor 48d3335f12 fix: auth redir memoization 2026-06-11 12:09:22 -04:00
soconnor 1c24c90189 fix: build errors 2026-06-11 12:01:19 -04:00
Claude 6b1173b4db Switch from standalone to standard Next.js build
Removes output: "standalone" from next.config.js, updates build/start/preview
scripts to use next build/next start, and updates Dockerfile to copy .next
output directory and run via bun run start instead of server.js.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 15:50:38 +00:00
Claude 17971f2a98 Disable React Compiler to fix SSR prerender on Linux
The React Compiler (reactCompiler: true) generates code that calls
react/compiler-runtime during SSR, which resolves to null on Linux/bun,
causing "Cannot read properties of null (reading 'useRef')" on every
statically prerendered page. Disabling restores normal SWC transforms.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 15:47:48 +00:00
soconnor 7a6e255325 Merge pull request #2 from soconnor0919/claude/time-clock-invoice-g342lt
Fix Docker build: force-dynamic dashboard layout
2026-06-11 11:43:26 -04:00
Claude b10cf5dd2e Force dynamic rendering for all dashboard pages
Dashboard pages require authentication and include client components
with hooks that fail during static prerendering on Linux/Docker.
Setting force-dynamic in the layout prevents build-time prerendering
across the entire authenticated dashboard.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 06:18:00 +00:00
soconnor a3d8120059 package updates 2026-06-11 01:49:06 -04:00
Claude 9c135d3289 Fix ESLint type import and add better-auth to serverExternalPackages
Prevents Turbopack from statically analyzing better-auth's kysely adapter,
which imports symbols removed in kysely 0.29. Also fixes @typescript-eslint
consistent-type-imports warning on the Db type alias.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 05:45:55 +00:00
Claude e03c553b7f Add temporary PDF preview links via public token TTL
- Schema: add publicTokenExpiresAt to invoices table
- invoices.generatePublicToken: accepts optional ttlHours param; sets
  expiry timestamp when provided
- invoices.getByPublicToken: returns 403 if token is expired
- New route GET /api/i/[token]/pdf: streams the invoice PDF publicly,
  returns 410 Gone if expired; excluded from auth middleware
- MCP invoices_generate_public_token: exposes ttlHours, returns both
  webUrl (/i/{token}) and pdfUrl (/api/i/{token}/pdf)

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 05:37:17 +00:00
Claude c0a333710f Complete MCP coverage: 58 tools, invoice filters, templates, email config, profile
MCP expanded from 49 to 58 tools:
- templates_list, templates_list_by_type, templates_create, templates_update,
  templates_delete — full invoice template CRUD
- businesses_get_email_config, businesses_update_email_config — configure
  per-business Resend API key and sending domain
- profile_get, profile_update — user profile read/write

invoices_list now accepts optional status and clientId filters (e.g. list
all draft invoices, or all invoices for a specific client). Backed by a
new optional input on invoices.getAll in the tRPC router.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 05:33:13 +00:00
Claude c6b6641dfa Expand MCP from 24 to 49 tools — full coverage of all major features
New tools added:

Expenses (5): expenses_list, expenses_get, expenses_create,
expenses_update, expenses_delete

Recurring invoices (7): recurring_list, recurring_create,
recurring_update, recurring_pause, recurring_resume,
recurring_generate_now, recurring_delete

Dashboard (1): dashboard_get_stats — revenue, pending, overdue,
client count, MoM change, 6-month chart, recent invoices

Invoice extras (8): invoices_get_current_open, invoices_send (email
with PDF attachment), invoices_send_reminder, invoices_generate_public_token,
invoices_revoke_public_token, invoices_bulk_update_status,
invoices_bulk_delete

New Zod schemas and JSON schemas for all added tools.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 05:30:16 +00:00
Claude 868c34f005 Expose invoiceId in MCP time_clock_in tool
The backend now accepts invoiceId on clock-in to link a timer directly
to a specific invoice. Expose this in the MCP so AI clients can clock in
against a particular invoice rather than relying on latest-invoice lookup.
Also update time_clock_out description to reflect the new behavior.

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 05:11:54 +00:00
Claude feb8f36ce7 Integrate time clock directly into invoices, remove standalone page
- Remove "Time Clock" from sidebar navigation
- Redirect /dashboard/time-clock to /dashboard/invoices
- Add InvoiceTimerCard to invoice detail page (shown for non-paid invoices)
- Timer started from an invoice is explicitly linked to that invoice at clock-in
- On clock-out, time is added directly to the linked invoice as a line item
- Active timer widget on dashboard now shows which invoice is being tracked
- Backend: clockIn accepts invoiceId; clockOut prefers explicit invoiceId over
  searching for the latest draft invoice for the client

https://claude.ai/code/session_014126WHVRT8mftmqkU6dajG
2026-06-11 00:23:50 +00:00
soconnor 0d8efb0c5f chore: update dependencies (next, react, radix-ui, trpc, drizzle, etc.)
- next 16.2.4 → 16.2.7, react/react-dom 19.2.5 → 19.2.7
- @trpc/* 11.7 → 11.17, drizzle-orm 0.44 → 0.45, drizzle-kit 0.30 → 0.31
- tailwindcss/postcss 4.1 → 4.3, better-auth 1.4 → 1.6
- @tanstack/react-query 5.90 → 5.101, framer-motion, recharts, fuse.js
- All @radix-ui patch/minor bumps, prettier 3.6 → 3.8, typescript-eslint 8.60
- Skipped majors: zod, eslint, typescript, lucide-react, resend, react-day-picker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:57:40 -04:00
soconnor 9762ede7ec feat: show live estimated hours/earnings while timer is running
Summary cards now include the in-progress session's hours and earnings,
rounded up to the nearest 15-min increment (matching clock-out billing
logic). A secondary "+Xh est." line appears below each stat when a timer
is active, updating every second as the elapsed counter ticks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:54:05 -04:00
soconnor 2b4608bfb4 feat: add missing MCP tools and fix undefined content response
- Add time_entries_update, time_entries_delete, time_entries_get_summary tools
- Fix textResult to use `data ?? null` so JSON.stringify always returns a
  string — procedures returning undefined (e.g. getRunning with no timer,
  businesses.getById not found) were dropping the text field from the MCP
  content item, causing client SDK validation failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:46:30 -04:00
soconnor c490556a02 fix: copy static assets into standalone dir after next build
next build with output:standalone does not automatically copy
.next/static or public into .next/standalone/. The standalone
server.js looks for them at __dirname/.next/static and
__dirname/public, so they must be copied there post-build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:31:20 -04:00
soconnor 839016532c fix: move ssr:false dynamic imports into client component
Turbopack disallows ssr:false in Server Components. Extracted the three
recharts dynamic imports into charts-client.tsx ("use client"), which
page.tsx now imports from directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:26:42 -04:00
soconnor b1f0c98fdd fix: use node standalone server instead of next start
Dynamically import recharts components with ssr:false to prevent
server-side rendering of DOM-dependent charts, eliminating the
width/height -1 warnings and redacted SSR errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:24:59 -04:00
soconnor d2f39bfc35 refactor: replace custom migrate.ts with standard Drizzle migrator
The baseline/bogus-entry logic was a one-time workaround for a db:push
→ migration transition that is long past. It required a per-migration
isMigrationApplied case — missing cases caused today's invoiceId bug.
Now both db:migrate (dev) and Docker startup use the same standard
migrate() call. Drizzle's own tracking table handles idempotency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:12:01 -04:00
soconnor 960f0c2d17 fix: add isMigrationApplied cases for migrations 0009–0012
Baseline logic was falling through to false for all migrations after
0008, causing incorrect behaviour on fresh deploys. Now correctly
detects api_keys table, time_entry table, invoiceId column, and
verification token value column type.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:07:35 -04:00
soconnor ef94b69e52 feat: show active timer on dashboard homepage with clock-out button
Adds a banner below the page header when a timer is running: shows
description, client, elapsed time (live), a Stop button that auto-links
to the latest invoice, and a Details link to the time clock page.
Query is prefetched server-side so the widget is instant on load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:05:54 -04:00
soconnor 540150be33 fix: use node standalone server instead of next start
output: standalone doesn't support next start — use node .next/standalone/server.js

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:00:32 -04:00
soconnor d9e2bab779 feat: time clock links to latest client invoice + fix SSO verification token overflow
- Clock out and manual entry creation now auto-add a line item to the
  client's latest draft/sent invoice and return invoice info
- Time clock page shows invoice badge on each entry with a link
- Toast after clock-out/create includes "View Invoice" action when linked
- MCP time_clock_in now accepts optional startedAt for backdating
- MCP time_clock_out description updated to document invoice linking
- Migration 0012: widen beenvoice_verification_token.value to text to
  fix varchar(255) overflow during Authentik PKCE OAuth flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 17:55:26 -04:00
soconnor 3c708b7914 time clock fixes 2026-06-06 17:18:32 -04:00
Claude b0e026c963 Fix ESLint errors: remove setState-in-effect, use nullish coalescing 2026-06-06 18:06:32 +00:00
Claude bdc25e0c58 Document Drizzle migration journal requirement in AGENTS.md 2026-06-06 18:03:55 +00:00
Claude cf7af6ae00 Add time_entries migration to Drizzle journal 2026-06-06 17:52:41 +00:00
Claude 29630ebc1f Add time clock feature with MCP access
- New beenvoice_time_entry DB table with migration (startedAt/endedAt, hours, rate, clientId)
- tRPC router with clockIn, clockOut, getRunning, getAll, getSummary, create, update, delete
- Dashboard page at /dashboard/time-clock with live elapsed timer, entry list, and manual entry form
- 5 MCP tools: time_clock_in, time_clock_out, time_get_running, time_entries_list, time_entries_create
- Sidebar navigation entry

Timer state is stored in PostgreSQL (endedAt IS NULL = running), suitable for serverless/Coolify deployment.
2026-06-06 17:05:17 +00:00
soconnor 413eb3e3c0 Allow MCP API route through proxy 2026-06-04 21:44:24 -04:00
soconnor bad24aeda4 Merge MCP API access 2026-06-04 21:40:08 -04:00
soconnor 37eb70be65 Add MCP API access 2026-06-04 21:33:32 -04:00
soconnor a13992e387 fix: use custom migrate.ts instead of drizzle-kit migrate in Docker
drizzle-kit migrate exits 0 even when migrations fail, so the server
would start on a broken schema. The custom migrate.ts uses drizzle-orm's
programmatic migrator and calls process.exit(1) on failure, which
prevents the server from starting and makes failures visible in Coolify.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:59:46 -04:00
soconnor f4c2435ddc fix: run db:migrate at container startup instead of build time
Migration now runs at container start (when DB is available) rather than
during the build stage (when no DB is reachable). The release image now
includes node_modules, drizzle.config.ts, and the drizzle/ folder so
drizzle-kit can apply pending migrations on each deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:48:21 -04:00
soconnor 898422571f fix: remove hardcoded baseURL from auth client
NEXT_PUBLIC_ vars are baked in at build time, so NEXT_PUBLIC_APP_URL=localhost
was being embedded in the bundle. Removing baseURL lets better-auth use
window.location.origin automatically, which works correctly on any domain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:41:05 -04:00
soconnor e3b2de5aa2 fix: replace invalid ADD CONSTRAINT IF NOT EXISTS with DO $$ BEGIN blocks
PostgreSQL does not support IF NOT EXISTS for ADD CONSTRAINT. Use PL/pgSQL
DO blocks to guard constraint creation idempotently instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 18:29:49 -04:00