Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Trishul CRM

A role-scoped CRM for an Indian sales organisation — customers, pipeline, tasks and team performance in one place, with an AI analyst built in and a cinematic opening sequence on the front door.

Dashboard

Leads Kanban


Demo accounts

Seeded and ready. The login screen has a one-click button for each, so a reviewer never has to type.

Role Email Password Sees
Admin admin@trishul.com Admin@123 Everything, plus Employees, Settings and the AI Assistant
Supervisor supervisor@trishul.com Super@123 Only their own records and those of their direct reports
Executive user@trishul.com User@123 Only records assigned to them

Sign in as each in turn — the dashboard counters, tables, board and reports all change, because scoping is enforced in the API, not the UI.


Run it

Prerequisites: Node.js 20.9+, PostgreSQL 14+ running locally.

# 1. Create the database
createdb revoras          # or: psql -U postgres -c "CREATE DATABASE revoras;"

# 2. API
cd server
npm install
cp .env.example .env      # defaults already match the setup below
npm run migrate           # 7 sequential migrations
npm run seed              # 45 customers, 70 leads, 40 tasks, 9 users
npm run dev               # http://localhost:5000

# 3. Web app — in a second terminal
cd client
npm install
cp .env.example .env.local
npm run dev               # http://localhost:3000

Open http://localhost:3000. The opening sequence plays once, then the login screen appears.

npm run db:reset in server/ rolls everything back and rebuilds from scratch. The seed is deterministic, so a reset reproduces the same data.

Environment

server/.env

PORT=5000
DATABASE_URL=postgresql://postgres@localhost:5432/revoras
JWT_SECRET=thisisasecretkeyforjwt_tokengeneration
CLIENT_URL=http://localhost:3000
NODE_ENV=development

# Optional. Without it the assistant answers from SQL instead — see below.
ANTHROPIC_API_KEY=
AI_MODEL=claude-opus-5

client/.env.local

NEXT_PUBLIC_API_URL=http://localhost:5000/api

Both .env files are gitignored; .env.example is committed for each.


What's in it

Dashboard — animated count-up counters, monthly lead flow, customer growth, a pipeline donut, a live activity feed, latest customers / newest leads, and a team leaderboard. Every figure comes from /api/reports/summary; nothing is hardcoded.

Customers — searchable, filterable, sortable table with pagination; an animated create/edit modal; delete behind a confirmation; and a detail drawer showing the account's related tasks and its full audit trail.

Leads — a drag-and-drop Kanban across the five pipeline stages plus a table view. Cards move optimistically and roll back if the API rejects the change. Dropping onto Won opens the conversion dialog, because winning a lead is what creates the customer.

Tasks — list and board views. The list groups by urgency (overdue, due today, upcoming, completed) rather than by date, so what matters surfaces without filtering. Completing a task draws its tick and pulses a ring.

Employees (admin) — account CRUD, role and reporting-line assignment, inline activate/deactivate, and a recursive org chart with per-person workload.

Reports — date-range filtered, with lead volume, pipeline value, customers won, revenue added, source and task breakdowns, a leaderboard, and working Export PDF and Export Excel buttons. Both files carry the company name from Settings in the header.

AI Assistant (admin) — chat grounded in live CRM aggregates, streamed token by token over SSE.

Settings (admin) — company details, logo, currency, theme, password.


Two decisions worth explaining

The AI Assistant works with no API key

The backend gathers live aggregates — counts, pipeline, overdue work, top performers, inactive accounts — and passes them to Claude, streaming the reply back over Server-Sent Events.

When ANTHROPIC_API_KEY is absent, a deterministic local analyst answers the same questions directly from SQL. All five suggested prompts are covered: summarise today's activity, draft a follow-up email, write a proposal, list inactive customers, and rank the team. It streams through the identical code path, so the feature demos correctly offline, on a laptop, with no key — and falls back automatically if the provider errors or declines.

Chart colours were computed, not chosen

The five-series palette was run through a colour-vision validator rather than picked by eye. Both themes pass every gate:

Light Dark
OKLCH lightness band
Chroma floor
Worst adjacent CVD ΔE (target ≥ 8) 15.9 16.0
Worst normal-vision ΔE (floor ≥ 15) 20.6 19.7
Contrast vs surface (≥ 3:1)

The dark column is the same five hues re-stepped for the dark surface, not an automatic flip. Counts and rupee values are never plotted on a shared axis.


Architecture

trishul-crm/
├── client/                        Next.js 16 · App Router · TypeScript strict
│   ├── public/trishul-mark.svg
│   └── src/
│       ├── app/
│       │   ├── page.tsx           Opening sequence (once per session)
│       │   ├── login/
│       │   └── (app)/             Authenticated shell + route guard
│       │       ├── dashboard/  customers/  leads/  tasks/
│       │       └── employees/  reports/  ai-assistant/  settings/
│       ├── components/
│       │   ├── ui/                DataTable, Modal, Drawer, StatCard,
│       │   │                      PageHeader, EmptyState, ConfirmDialog,
│       │   │                      Toolbar, Pagination, Form, Badge, …
│       │   ├── charts/            Recharts wrappers with the house rules
│       │   ├── layout/            Sidebar, Topbar, AuthGate, transitions
│       │   ├── brand/             Inline-SVG trishul
│       │   ├── intro/             GSAP + tsparticles sequence
│       │   └── customers|leads|tasks|employees/
│       ├── hooks/                 useResourceList, useCountUp, useDebounce…
│       ├── lib/                   api (axios), export (PDF/Excel), markdown,
│       │                          utils (currency, dates, text)
│       ├── store/                 Zustand: auth, ui
│       └── types/
└── server/                        Express 5 · ES modules
    ├── knexfile.js
    └── src/
        ├── db/
        │   ├── knex.js            One shared instance
        │   ├── migrations/        7 sequential files
        │   └── seeds/
        ├── middleware/            protect · authorize · scopeToRole · errors
        ├── routes/ → controllers/ → services/
        ├── validators/            express-validator chains
        └── utils/                 ApiError, asyncHandler, pagination, jwt

The layering rule: routes wire middleware, controllers stay thin, services own the SQL. No route needs try/catch — an asyncHandler wrapper forwards rejections to one centralized error middleware that returns a consistent { message, errors? }.

Why these choices

PostgreSQL + Knex, no ORM. The reporting queries — gap-filled monthly series, correlated per-employee aggregates, trigram search — are where the work is. A query builder expresses them directly; an ORM would mean fighting it. Lead conversion runs in a transaction, so a won lead can never point at a customer that was not created.

Role scoping in middleware, never the client. scopeToRole computes the caller's visible record set and every service turns it into a WHERE clause. The client-side guard only decides what to render. sortBy is checked against a per-resource allow-list before it reaches Knex.

Tailwind v4 with one token file. Every colour, radius, shadow and easing curve is declared once in globals.css and mapped onto utilities, so both themes stay in step and no component invents its own value.

Zustand over a data-fetching library. Auth and UI state are small and global; lists own their own state through one shared useResourceList hook that also ignores superseded responses, so fast typing can't let a slow reply overwrite a newer one.


API

All under /api. List endpoints accept ?page, ?limit, ?search, ?status, ?assignedTo, ?sortBy, ?order and return { data, pagination: { page, limit, total, totalPages } }.

Method Route Notes
POST /auth/login · /auth/logout JWT in an httpOnly cookie, header fallback
GET PATCH /auth/me · /auth/profile · /auth/password
GET POST PATCH DELETE /customers · /customers/:id Detail includes tasks + activity
GET POST PATCH DELETE /leads · /leads/:id Plus /leads/board
POST /leads/:id/convert Lead → customer, in one transaction
GET POST PATCH DELETE /tasks · /tasks/:id Plus /board, /stats, /:id/complete
GET POST PATCH DELETE /users · /users/:id Admin only; plus /hierarchy, /:id/supervisor
GET /users/assignable Scope-limited; any role
GET /reports/summary · /reports/analytics Role-scoped
GET PATCH /settings Read: any role · Write: admin
GET POST /ai/status · /ai/chat Admin only; SSE stream

Verification

  • npm run migrate && npm run seed builds a populated database from scratch and prints the record counts.

  • 53 API checks pass against a freshly seeded database, covering the three demo logins, permission boundaries, search-filter-sort-paginate, the Kanban board, conversion and its double-conversion guard, validation shapes, reports, and all five AI prompts.

  • Role scoping measured against the seeded data — the same request returns a different slice for each account:

    Signed in as Customers Leads Tasks
    Admin 45 70 40
    Supervisor 24 34 20
    Executive 6 8 5
  • npm run build compiles all 11 routes with TypeScript strict mode and no any.

  • Both exports were downloaded from the running app and parsed back: the workbook opens with six sheets (Summary, Pipeline, Sources, Monthly, Team, Tasks) carrying the company name, period and scope in its header, and the PDF is a valid two-page document.

Not verified end to end: dragging a Kanban card was checked by code and by its keyboard/touch fallback rather than by a synthetic drag — Chrome does not raise native HTML5 drag events from automated input. Layouts were checked at desktop width in a real browser and rely on standard responsive utilities below that.


Stack

Frontend — Next.js 16 (App Router, TypeScript strict, src/, @/*), Tailwind CSS v4, Motion, GSAP + tsparticles, Recharts, Zustand, Axios, react-hook-form + zod, sonner, lucide-react, jsPDF + jspdf-autotable, SheetJS.

Backend — Node.js + Express 5 (ES modules), PostgreSQL with Knex.js, pg, JWT + bcryptjs, cors, dotenv, cookie-parser, morgan, express-validator.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages