A modular ERP for growing operations, built in public.
WardSuite is a personal ERP built from frustration with existing tools that are either too expensive, too rigid, or missing the cross-module visibility that operations teams actually need. Starting with CRM and Supply Chain, expanding toward a full business operating system.
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite 6, Tailwind CSS v4 |
| Animations | Motion (Framer Motion v12) |
| State | Zustand 5 (persisted) |
| Backend | Express 4, TypeScript, tsx |
| Database | Firestore (Firebase Admin SDK) — repository pattern, swappable |
| Drag & Drop | @hello-pangea/dnd |
| Tables | TanStack Table v8 |
| Monorepo | NX |
| Build | Vite (web) + esbuild (API) |
| Deployment | Docker + PM2 |
wardsuite/
├── apps/
│ ├── web/ # React SPA (Vite + Tailwind)
│ └── api/ # Express API server
│
├── libs/
│ ├── shared/
│ │ ├── types/ # @wardsuite/shared/types
│ │ ├── utils/ # @wardsuite/shared/utils
│ │ ├── auth/ # @wardsuite/shared/auth (Zustand store)
│ │ └── ui/ # @wardsuite/shared/ui (design system)
│ │
│ └── crm/
│ ├── domain/ # @wardsuite/crm/domain (entities + DTOs)
│ ├── data-access/ # @wardsuite/crm/data-access (repos + adapters)
│ ├── feature-leads/ # @wardsuite/crm/feature-leads
│ ├── feature-customers/ # @wardsuite/crm/feature-customers
│ └── ui/ # @wardsuite/crm/ui
│
├── CLAUDE.md
├── tsconfig.base.json
├── nx.json
├── Dockerfile
├── docker-compose.yml
└── ecosystem.config.js
- Node.js 22+
- Firebase project with Firestore enabled
- Service account key (for Admin SDK)
git clone https://github.com/wardvisual/wardsuite.git
cd wardsuite
npm installCreate .env at the workspace root:
PORT=3000
NODE_ENV=development
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=your-service-account@project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
FIREBASE_DATABASE_ID=
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_DATABASE_ID=npm run dev # API (:3000) + Vite (:5173) in parallel
npm run dev:api # API only
npm run dev:web # Frontend only (proxies /api → localhost:3000)npm run firebase:seed| Password | Role | |
|---|---|---|
| admin@wardsuite.com | admin123 | ADMIN |
| manager@wardsuite.com | manager123 | MANAGER |
| staff@wardsuite.com | staff123 | STAFF |
| Feature | Status |
|---|---|
| Leads | ✅ Kanban + table, CSV import with column mapper, drag-and-drop status |
| Customers | ✅ Full CRUD, convert from lead |
| Pipeline | ✅ Deal Kanban, drag-and-drop stage transitions, revenue totals per stage |
| Activities | ✅ Timeline log with type filters, automatic audit trail |
| Dashboard | ✅ Live revenue stats, 12-month chart, conversion metrics |
| Feature | Status |
|---|---|
| Suppliers | ✅ Supplier management, status tracking |
| Products | ✅ Product catalog with stock levels and reorder alerts |
| Purchase Requests | ✅ Request lifecycle management |
| Stock Movements | ✅ Inbound/outbound inventory tracking |
- Lead CRUD with kanban and status pipeline
- Lead-to-customer conversion with audit trail
- Customer full CRUD
- Deal pipeline with drag-and-drop stage management
- Activities/timeline log
- Dashboard with live revenue analytics
- Automatic audit trail on all CRM mutations
- Lead scoring (Low / Medium / High / Hot)
- Customer segments / tags
- Deal probability % and weighted pipeline value
- Bulk lead import validation and error reporting
- Lead assignment between team members
- Deal close date notifications / overdue alerts
- Full-text search across leads, customers, deals
- Revenue forecast view
- Win/loss reason tracking
- Deal history log
- Duplicate lead detection on import
- Customer LTV calculation
- In-app notifications
- Email integration (SendGrid / Resend)
- Activity reminders
- CRM analytics (conversion funnel, lead source breakdown)
- Exportable reports (PDF / CSV)
- Team performance dashboard
- Role-based field visibility
- Invite by email
- Password change and session management
- 2FA (TOTP)
Services code against interfaces, not concrete classes. Swapping Firestore for Postgres means implementing the interface — no changes to service code.
export interface ILeadRepository {
findAll(): Promise<Lead[]>;
create(dto: CreateLeadDto, actorId?: string): Promise<Lead>;
}
// Today
const repo = new FirestoreLeadRepository(db);
// Future
const repo = new PostgresLeadRepository(pgClient);Every CRM mutation (create, update, delete, stage change) is logged automatically:
Lead "Acme Corp" (LD-042) created from source: referral.
Deal "Q3 Renewal" moved from "proposal" → "won".
Customer "TechBase Ltd" updated — status "inactive" → "active".
POST /api/auth/login → { token, user }
→ stored in Zustand (localStorage persist)
→ injected as Bearer token on every request
→ 401 auto-clears session + redirects to /login
# Docker
npm run docker:build
npm run docker:up
# PM2
npm run build
npm run start:pm2| Script | Description |
|---|---|
npm run dev |
API + Vite dev servers in parallel |
npm run build |
Build frontend + bundle API |
npm run lint |
TypeScript type check |
npm run firebase:seed |
Seed Firestore with demo data |
npm run docker:up |
Start Docker containers |
npm run start:pm2 |
Start with PM2 in cluster mode |
Eduardo — @wardvisual
MIT © 2025 Eduardo
