███╗ ██╗███████╗██╗ ██╗███████╗██╗ ██████╗ ██╗ ██╗
████╗ ██║██╔════╝╚██╗██╔╝██╔════╝██║ ██╔═══██╗██║ ██║
██╔██╗ ██║█████╗ ╚███╔╝ █████╗ ██║ ██║ ██║██║ █╗ ██║
██║╚██╗██║██╔══╝ ██╔██╗ ██╔══╝ ██║ ██║ ██║██║███╗██║
██║ ╚████║███████╗██╔╝ ██╗██║ ███████╗╚██████╔╝╚███╔███╔╝
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝
Intelligent Lead Pipeline Management
Live App → · Architecture · Engineering Deep-Dive · Getting Started
Before NexFlow, four business entities (HSL, MGL, MKL, HCL) each ran their own Excel sheet. Lead tracking meant version-conflicted spreadsheets, no cross-team visibility, zero accountability when a lead fell through, and no way to answer the basic question: what happened to this customer?
NexFlow replaces that chaos with a structured pipeline — from first marketing contact to closed deal — with every step tracked, every role in control of exactly what they should see, and every change permanently logged.
┌─────────────────────────────────────────────────────────────────┐
│ NexFlow Platform │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │Marketing │ │ Sales │ │ Manager │ │ Super Admin │ │
│ │ Layer │ │ Layer │ │ Layer │ │ Layer │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ ┌────▼──────────────▼──────────────▼─────────────────▼──────┐ │
│ │ API Security Stack │ │
│ │ Session → Role → BU Scope → Dept Scope → Field Filter │ │
│ └────────────────────────────┬────────────────────────────── ┘ │
│ │ │
│ ┌────────────────────────────▼────────────────────────────── ┐ │
│ │ PostgreSQL (Prisma) │ │
│ │ leads · users · business_units · departments · audit_log │ │
│ └─────────────────────────────────────────────────────────── ┘ │
└─────────────────────────────────────────────────────────────────┘
| Layer | Technology | Why |
|---|---|---|
| Framework | Next.js 14 (App Router) | SSR, API routes, middleware — one codebase |
| Language | TypeScript | End-to-end type safety from DB schema to UI |
| Database | PostgreSQL (Neon / Supabase) | Relational, audit-friendly, ACID transactions |
| ORM | Prisma | Type-safe DB access, atomic transactions for REQ codes |
| Auth | NextAuth.js v5 | JWT sessions with role + BU data embedded |
| Styling | Tailwind CSS + Shadcn/ui | Dark design system, accessible primitives |
| Charts | Recharts | Pipeline analytics, KPI trends, sector heatmaps |
| Voice | Whisper AI | Egyptian Arabic transcription, mixed Arabic/EN |
| Excel | xlsx | Import historical data, export filtered views |
| PWA | Service Worker | Installable on iOS/Android, offline shell caching |
| Hosting | Vercel + Neon | Zero-config deploys, serverless PostgreSQL |
Every lead gets a unique identifier on creation:
HSL · 5 · 06 · 24 · 0001
↑ ↑ ↑ ↑ ↑
Entity Year Month Day Daily sequence (per BU)
The naive approach — SELECT MAX(sequence) + 1 — breaks under concurrent submissions. NexFlow uses a lead_sequences table with an atomic UPDATE ... RETURNING pattern inside a Prisma transaction:
// lib/req-code.ts
export async function generateReqCode(
tx: Prisma.TransactionClient,
businessUnitId: string,
prefix: string
): Promise<string> {
const today = new Date().toISOString().slice(0, 10); // "2025-06-24"
const seq = await tx.leadSequence.upsert({
where: { businessUnitId_date: { businessUnitId, date: today } },
create: { businessUnitId, date: today, sequence: 1 },
update: { sequence: { increment: 1 } },
});
const [y, m, d] = today.split("-");
const code = `${prefix}${y.slice(-1)}${m}${d}${String(seq.sequence).padStart(4, "0")}`;
return code; // → "HSL506240001"
}Result: multiple marketing reps can submit leads simultaneously with zero duplicate REQ codes, zero locks held at the application layer.
The most important architectural decision: field visibility is enforced at the API layer, not the UI layer.
Every lead field belongs to one of three permission groups:
| Group | Who can view | Who can edit |
|---|---|---|
MUTUAL |
All roles | Marketing (on create) |
MARKETING |
Marketing, Manager, Admin | Marketing only |
SALES |
Sales (own dept), Manager, Admin | Sales only |
The field filter runs on the server before the response leaves:
// lib/field-permissions.ts
export function filterLeadForRole(lead: Lead, role: Role): Partial<Lead> {
const mutual = pickMutualFields(lead);
const marketing = role !== "SALES" ? pickMarketingFields(lead) : {};
const sales = role !== "MARKETING" ? pickSalesFields(lead) : {};
return { ...mutual, ...marketing, ...sales };
}A Sales user's network tab will never contain a leadSource, marketingNotes, or directedToDepartmentId — those keys don't exist in the response payload.
Every protected route runs through this chain in order — a request failing any check is rejected before reaching the next:
1. Session check → 401 Unauthorized if no valid JWT
2. Role check → 403 Forbidden if role lacks permission for this action
3. BU scope check → 403 if lead's business unit is not in user's BU list
4. Dept scope check → 403 if Sales user's department ≠ lead's directed department
5. Field filter → Strip fields not visible to this role before responding
This means a compromised Sales session cannot read Marketing strategy data even with a crafted request — the BU and field filters run unconditionally.
The handoff notification was designed to swap providers without touching call-sites:
// lib/whatsapp.ts
interface WhatsAppAdapter {
buildUrl(to: string, message: string): string;
}
class WaMeAdapter implements WhatsAppAdapter {
buildUrl(to: string, message: string) {
return `https://wa.me/${to}?text=${encodeURIComponent(message)}`;
}
}
// v2: class MetaBusinessApiAdapter implements WhatsAppAdapter { ... }
// v2: class TwilioAdapter implements WhatsAppAdapter { ... }
export const whatsapp = new WaMeAdapter(); // swap via WHATSAPP_PROVIDER env varThe card template itself is configurable per business unit — each entity can define which fields appear and in what order, managed in-app by Super Admin.
Standard speech-to-text APIs fail on Egyptian dialect Arabic, especially mixed with English technical terms (product names, measurements). NexFlow routes audio through OpenAI Whisper with explicit language prompting:
// app/api/transcribe/route.ts
const transcription = await openai.audio.transcriptions.create({
file: audioBlob,
model: "whisper-1",
language: "ar",
prompt: "نص باللغة العربية المصرية، قد يحتوي على مصطلحات إنجليزية تقنية",
});The prompt parameter primes Whisper with Egyptian Arabic context, dramatically improving accuracy for mixed-language input. The voice button appears on every text field in the lead form.
Every field change on every lead is logged to lead_history before the update commits:
model LeadHistory {
id String @id @default(cuid())
leadId String
userId String
field String // e.g. "requestStatus"
oldValue String?
newValue String?
changedAt DateTime @default(now())
lead Lead @relation(fields: [leadId], references: [id])
user User @relation(fields: [userId], references: [id])
}The UI renders this as a chronological timeline per lead: who changed what, when, and what the value was before. Super Admin has a system-wide audit view across all leads and all entities.
Onboarding existing data was a hard requirement. The import flow:
- User uploads
.xlsx— parsed client-side withxlsx - Column auto-mapper suggests field matches based on header text similarity
- Preview renders with per-row validation (missing required fields, malformed dates, duplicate REQ codes highlighted)
- Confirmed rows bulk-insert with a
isHistorical: trueflag - Historical leads skip WhatsApp notifications and audit trail triggers
Historical data became searchable and contributed to analytics from Day 1 of deployment.
| Feature | Status |
|---|---|
| Multi-entity lead pipeline (4 BUs) | ✅ Live |
| Field-level RBAC (Marketing / Sales / Manager / Admin) | ✅ Live |
| REQ Code generation (atomic, conflict-free) | ✅ Live |
| WhatsApp lead card handoff | ✅ Live |
| Lead search by REQ code | ✅ Live |
| Analytics dashboard (KPIs, charts, filters) | ✅ Live |
| Audit trail (per-field change history) | ✅ Live |
| Voice transcription — Egyptian Arabic (Whisper AI) | ✅ Live |
| Excel import with validation & conflict detection | ✅ Live |
| Excel export (filtered views) | ✅ Live |
| PWA — installable, offline shell | ✅ Live |
| Mobile bottom navigation bar | ✅ Live |
| In-app notifications | ✅ Live |
| Admin: user + BU + department management | ✅ Live |
| Admin: configurable dropdown values | ✅ Live |
| Admin: per-BU lead card template editor | ✅ Live |
| WhatsApp Business API (programmatic send) | 🗓️ v2 |
| Lead comments thread (per-lead conversation) | 🗓️ v2 |
| AI lead scoring by sector/source patterns | 🗓️ v2 |
| Multi-tenant SaaS (other companies) | 🗓️ v2 |
git clone https://github.com/ws0x/nexflow.git
cd nexflow
npm installcp .env.example .envEdit .env:
DATABASE_URL="postgresql://..." # Your Neon/Supabase connection string
AUTH_SECRET="..." # node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
AUTH_URL="http://localhost:3000"
NEXT_PUBLIC_APP_URL="http://localhost:3000"
NEXT_PUBLIC_APP_NAME="NexFlow"
# Optional: OpenAI for voice transcription
OPENAI_API_KEY="sk-..."npm run db:push # Push schema to your database
npm run db:seed # Seed BUs, dropdowns, and a Super Admin accountDefault Super Admin credentials (change immediately after first login):
- Email:
admin@nexflow.com - Password:
NexFlow@Admin2025
npm run devOpen http://localhost:3000.
nexflow/
├── docs/
│ ├── architecture.md # System design & data flow
│ ├── schema.md # Database schema reference
│ ├── permissions.md # RBAC field permission rules
│ ├── roadmap.md # Phase-by-phase build plan
│ └── deployment.md # Vercel + Neon deployment guide
├── prisma/
│ ├── schema.prisma # Database schema
│ └── seed.ts # Initial BUs, dropdowns, admin user
├── src/
│ ├── app/
│ │ ├── (auth)/login/ # Login page
│ │ ├── (dashboard)/
│ │ │ ├── leads/ # Lead list, detail, new lead form
│ │ │ ├── analytics/ # Manager dashboard
│ │ │ └── admin/ # Super Admin panel
│ │ ├── actions/ # Next.js Server Actions
│ │ └── api/ # REST endpoints (auth, leads, export)
│ ├── components/
│ │ ├── leads/ # Lead form, table, import wizard
│ │ ├── analytics/ # Chart components (Recharts)
│ │ ├── admin/ # Permissions matrix, user management
│ │ └── layout/ # Sidebar, header, notification bell
│ └── lib/
│ ├── req-code.ts # Atomic REQ code generator
│ ├── field-permissions.ts # Role-based field filter
│ ├── permissions.ts # Route & action guards
│ ├── whatsapp.ts # WhatsApp adapter
│ └── auth.ts # NextAuth configuration
└── public/
├── manifest.json # PWA manifest
└── sw.js # Service worker
Full guide in docs/deployment.md. Short version:
- Push schema to your database:
npm run db:push && npm run db:seed - Create a Vercel project, import this repo
- Set the environment variables listed above
- Deploy — Vercel runs
prisma generateautomatically viapostinstall
NexFlow was built for a company operating four business units in the industrial machinery and handling sector in Egypt. The system is in active daily use by Marketing, Sales, and Management teams across all four entities.
Key outcomes after deployment:
- Lead entry and routing reduced from hours (shared file synchronisation) to minutes
- Zero duplicate leads since switching from Excel to REQ-coded pipeline
- Full audit trail eliminated disputes about "who changed what"
- Managers have cross-entity pipeline visibility for the first time
Built by Yusuf Hakim · binhakim.dev