A ground-up rebuild of the Busy2Easy concept as a modern, local-first web app: Next.js + TypeScript + PostgreSQL (via Prisma), replacing the Excel-file backend and single-machine WinForms app with something you can actually grow, deploy, and hand off to other developers.
This skeleton is intentionally a skeleton — it's built to run, look and feel right, and be obviously extensible, so we can decide together which module to flesh out first with real business logic and API routes.
- Company Setup — the industry → sub-category cascading picker, transcribed
exactly from the existing
Form1.vb(ComboBox5/ComboBox8), plus the Fundamentals and Financial Details fields from the original design. - Accounting — the full chart of accounts from
General Ledger list.docx, organized by Asset/Liability/Equity/Revenue/Expense, ready to attach transactions to. - CRM, HR, Legal, Tax, Reports — module shells with the real tax taxonomy and report list from the source docs, ready for their own data models.
- Prisma schema (
prisma/schema.prisma) — Company → Departments (with a real self-referencing org hierarchy, not a flat list) → Accounts → Transactions → Contacts → Tax Records. One genericTransactionmodel replaces the ~20 near-identical VB.NET forms.
The sidebar now reflects the full intended information architecture — Home,
Business, People, Customers, Projects, Documents, Operations, Business
Assistant, Settings — 9 groups, ~35 items. Anything without a real page
behind it yet is marked Soon and isn't clickable; there are no dead
links pretending to be finished features. As modules get built, flip their
status from "soon" to "live" in src/lib/navigation.ts and add the
route — everything else (sidebar, mobile drawer, command palette) picks it
up automatically.
A toggleable command palette (src/components/assistant/NavigationAssistant.tsx):
- Open it via the floating button (bottom-right) or
Cmd/Ctrl+K - Type to search any of the ~35 nav items by name or group, fuzzy-matched
- Arrow keys + Enter to jump straight there — mouse optional
- Contextual tip at the bottom, keyed to whatever page you're currently on
(
src/lib/page-tips.ts) — add an entry there for any new page you build - Turn off assistant inside the palette disables it entirely (preference saved in the browser); a small "Enable guide" pill stays in the corner to turn it back on
- Phone / narrow tablet: the sidebar becomes a slide-in drawer opened from a top app bar (hamburger menu), tables scroll horizontally instead of breaking layout, and grids collapse to a single column.
- Tablet landscape / laptop / desktop: the fixed sidebar + top search bar layout from before.
- Smartboards / large displays: above 1800px viewport width, base font size and tap targets scale up so content stays legible from a distance and touch input stays reliable.
All of this is one codebase — there's no separate mobile build to maintain.
- Renamed VexBusiness → Busy2Easy throughout (package name, page title, sidebar wordmark).
- Real charts (via Recharts) on the dashboard — revenue vs. expenses trend and an expense breakdown — using illustrative sample data shaped like what the ledger will produce once transactions flow through Accounting.
- Motion (via Framer Motion) — animated count-up stats, staggered card reveals, a sliding active-tab indicator in the sidebar, and page-transition animations in the setup wizard.
- Company Setup is now a 4-step guided wizard instead of one long form — aimed at someone who doesn't want to parse "Fiscal Year End" and "VAT Number" on the same screen as their first field. Plain-language dashboard copy for the same reason ("See your money, clearly" instead of "Accounting").
- Country / State / Currency are now select-only (searchable, but you can only commit a value that's in the list) — no more typos or invalid entries.
- HR, Legal, and CRM now have real structured tables/sections instead of a bare upload button.
- No fabricated numbers anywhere — stats and charts show an honest "no data yet" state until real transactions exist.
- Responsive layout — see "Device compatibility" above.
Every page that fetches data used to call fetch(...).then(setState) (or an
async function with no try/catch) with no error handling at all. If that
fetch failed for any reason — a dropped connection, a non-JSON error page,
anything — the promise rejected silently, setState never ran, and the page
sat on its loading spinner forever with no indication anything was wrong.
That's what caused the dashboard to hang on "Loading dashboard data..." even
when the API itself was working correctly when tested directly.
Fixed properly, not patched around: every data-fetching page now uses a
shared fetchJSON helper (src/lib/fetch-json.ts) that throws a real,
readable error on failure, wrapped in try/catch, with a visible error
screen and a Retry button (src/components/ui/ErrorState.tsx) instead of
an infinite spinner. If something does go wrong now, you'll see what and be
able to retry — not guess.
If you hit the "stuck on Loading" bug again: open the browser's DevTools
(F12) → Network tab on the live page, reload, and look at the actual request
to whichever /api/... endpoint is failing — the status code and response
body will now be visible in the error screen too. Compare that to what you
get testing the same endpoint directly; if they differ, that's the next
thread to pull. Mock data was considered and deliberately rejected as the
fix here — the database, Prisma, and the API were already confirmed working;
the bug was purely a frontend error-handling gap, now closed everywhere.
Two real fixes applied, not just tuning:
- Every API route is now
export const dynamic = "force-dynamic"— Next.js was previously trying to pre-render some routes at build time, which meant database queries were running during the Vercel build itself. This is what caused theCan't reach database serverbuild failures. - Balance and dashboard queries now aggregate at the database level
(
groupBy/_sum) instead of fetching every transaction row and summing in JavaScript. The dashboard summary also only pulls the last 6 months of transaction detail — bounded, not unbounded — while still using an all-time aggregate for the headline balances. - Indexes added on every
companyIdforeign key (andaccountId,datewhere queries filter on them), so lookups stay fast as data grows instead of degrading linearly with table size.
If the live site still feels slow on first load after being idle: that's very likely Neon's free-tier "scale to zero" behavior — the database goes to sleep after inactivity and takes a moment to wake on the next request. That's expected free-tier behavior, not a bug in this codebase; a paid Neon tier (or periodic keep-alive pings) removes it if it matters for your use case.
Six modules that were "Soon" placeholders are now fully real — real Prisma models, real API routes, real UI:
- Notifications — a real activity log, auto-generated whenever a
transaction, contact, employee, asset, project, or bank account is added
elsewhere in the app (see
src/lib/notify.ts) - Assets — company equipment/vehicle/property register
- Inventory — stock levels with reorder-point tracking (flags low stock automatically, including a Notification)
- Projects & Tasks — projects with linked tasks, status updates inline
- Banking — linked bank accounts (reconciliation still happens through Accounting; this isn't live bank-feed integration)
- Files / Templates / Approvals — folded into the existing Documents module (Legal) as additional categories, rather than three more separate half-built pages
Still genuinely not implemented, and why: Inbox, Recruitment, Attendance, Sales, Marketing, Support, Purchasing, Manufacturing, Logistics, Insights, Automation, Workflows, Organization, Security, and Billing remain "Soon." Most of these need something bigger than a CRUD page to be real — email integration (Inbox), a rules/automation engine (Automation, Workflows), payment processing (Billing), or genuine multi-user authentication (Organization, Security) — and building shallow versions of them would mean more screens that look done but aren't, which is the opposite of what this project has been about. Worth tackling deliberately, one at a time, when you're ready for each.
A dark ink-teal ground with brass-gold and sage accents, serif display type (Fraunces) paired with a clean body sans (Inter) and a tabular monospace (IBM Plex Mono) for figures — evoking ruled ledger paper without looking like a spreadsheet. This was a deliberate choice to feel premium and trustworthy for financial data, distinct from generic SaaS templates.
Every module now reads and writes to Postgres for real, via API routes under
src/app/api/:
- Company Setup saves as you move between steps, and reloads your data on return. Departments are fully unlocked (no premium gate) and functional — add one, it's saved and listed immediately.
- Accounting shows real computed balances (a straight sum of recorded transactions per account — single-entry for this stage, not full double-entry debit/credit bookkeeping). Click any account to record a transaction; add custom accounts beyond the seeded chart.
- CRM — add a contact, it's saved and grouped by type immediately.
- Tax — click a register to log a real payable/receivable/refund entry; each card shows its running total.
- HR — Employees are fully real (add one, assign a department). Payroll Runs and Leave Requests are honestly labeled as not yet modeled — they need their own database tables before they can be real, same principle we've used throughout: no faked completeness.
- Legal — file upload is real. Files are written to
public/uploadson local disk and tracked in the database. - Dashboard — stats and charts now compute from your actual transactions. With no data yet, it honestly shows "No data yet" instead of a fake number; once you record transactions in Accounting, everything here fills in live.
One important simplification: there's no login system yet, so the whole
app currently operates as a single company (src/lib/current-company.ts).
Every API route reads/writes against one record. Real multi-user accounts
are a separate, bigger piece of work — worth doing once you're ready to
have more than one person or company using this.
Deployment note for file uploads: writing to local disk (public/uploads)
works for local dev and most traditional hosts, but Vercel's serverless
filesystem doesn't persist uploaded files between requests. If you deploy
Legal's file upload to Vercel, swap the disk write in
src/app/api/documents/route.ts for Vercel Blob or S3 — the database side
(name, category, fileUrl) doesn't need to change.
After the steps below, every button in the app does something real — nothing is a dead click.
You'll need Node.js 20+ and Docker installed locally — this sandbox has no network access, so dependencies haven't been installed yet.
# 1. Install dependencies
npm install
# 2. Start local Postgres
docker compose up -d
# 3. Set up your environment
cp .env.example .env
# 4. Push the schema and seed reference data (chart of accounts + industries)
npm run db:push
npm run db:seed
# 5. Run the app
npm run devOpen http://localhost:3000 — that's your "local website."
Already had this running before this update? Just run npm run db:push
again to add the new Employee/Document tables — your existing data stays put.
Browse the data visually any time with npm run db:studio.
src/app/ → pages (one folder per module: accounting, crm, hr, legal, tax, reports)
src/components/ → layout/, ui/, and per-module components
src/lib/ → reference data (chart of accounts, industries) + db client
src/services/ → (next) business logic — calculations, validation, rules
prisma/schema.prisma → the data model, version-controlled from day one
Each module is self-contained, so adding a new one (e.g. Inventory, Payroll) never requires touching existing modules — that's what makes this "customizable" in the way the original desktop app wasn't.
- Real & accurate: the chart of accounts, the industry/sub-industry taxonomy, the tax type list, the data model relationships.
- Stubbed (UI only, no API wiring yet): forms don't save, buttons don't submit, dashboard numbers are placeholders. This is the shell — next step is wiring up real API routes and forms module by module.
- Pick one module (Company Setup is the natural first one — everything else depends on it) and wire up real create/read/update via API routes + Prisma.
- Add authentication (NextAuth.js is the natural fit for this stack).
- Bring over the multi-language support from the original app (the design system already has room for it via a simple i18n dictionary layer).
- Layer in the Department/org-hierarchy premium feature once the base Company + Account models are proven out.