A modern, full-stack lead-capture and pipeline management application, built for the Digital Heroes Full Stack Development Internship Task.
🔗 Live App: lead-desk-livid.vercel.app
🔐 Admin Portal: lead-desk-livid.vercel.app/admin/login
💻 Source Code: github.com/Abhiho11a/leadDesk
- Overview
- Screenshots
- Tech Stack
- Architecture
- Data Model
- API Reference
- Authentication & Security
- Local Setup
- Testing the App
- Design Decisions
- AI Usage Disclosure
LeadDesk Mini solves a simple but common problem: capturing inbound leads from a public landing page and giving an admin a fast, searchable way to manage them through a sales pipeline (New → Contacted → Closed).
The app is split into two experiences:
| Side | Purpose |
|---|---|
| 🌐 Public | A landing page with a validated lead-capture form |
| 🔐 Admin | A password-protected dashboard to search, filter, update, and manage every submitted lead |
Key highlights:
- 🎨 Modern glassmorphic UI — animated gradient background, frosted-glass form cards, smooth transitions
- ✅ Dual-layer validation — real-time client-side checks + independent server-side schema enforcement, so the API can never be corrupted even if the frontend is bypassed
- 🔒 Session-based authentication —
httpOnlycookies, hashed passwords, server-side session invalidation on logout - ⚡ Live pipeline management — instant search, status filtering, and one-click status updates with optimistic UI
Screenshots live in
/docs/screenshotsin this repo.
Frontend
- ⚛️ React 18 + Vite
- 🎨 Tailwind CSS
- 🧭 React Router DOM v6
- 🔗 Axios (
withCredentials: truefor session cookies) - 🖼️ Lucide React (icons)
Backend
- 🟢 Node.js + Express
- 🍃 MongoDB + Mongoose
- 🔑
express-session+connect-mongo(session persistence) - 🔐
bcrypt(password hashing)
Deployment
- ▲ Frontend → Vercel
- 🚂 Backend → Render
- 🍃 Database → MongoDB Atlas
leadDesk/
├── frontend/ # React + Vite client
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Landing.jsx # public form + hero
│ │ │ ├── AdminLogin.jsx
│ │ │ └── AdminDashboard.jsx
│ │ ├── components/
│ │ └── services/api.js # centralized Axios instance
│ └── .env.example
│
├── backend/ # Node + Express API
│ ├── models/
│ │ ├── Lead.js
│ │ └── Admin.js
│ ├── routes/
│ │ ├── leads.js
│ │ └── auth.js
│ ├── middleware/
│ │ ├── validate.js
│ │ └── requireAuth.js
│ ├── seedAdmin.js
│ ├── server.js
│ └── .env.example
│
└── screenshots/
| Field | Type | Rules |
|---|---|---|
name |
String | required, trimmed, min 2 chars |
email |
String | required, valid email regex |
budgetRange |
String (enum) | <1k | 1k-5k | 5k-10k | 10k+ |
message |
String | required, max 1000 chars |
status |
String (enum) | New | Contacted | Closed — default New |
createdAt |
Date | auto-generated |
| Field | Type | Rules |
|---|---|---|
email |
String | required, unique |
passwordHash |
String | bcrypt hash — plaintext password is never stored |
Why enums instead of free text? Locking budgetRange and status to a fixed set of values keeps admin-side search/filter reliable and prevents inconsistent data (e.g. "closed" vs "Closed" vs "CLOSED") from ever entering the pipeline.
Base URL: https://<your-render-service>.onrender.com/api
| Method | Endpoint | Protected | Description |
|---|---|---|---|
GET |
/ |
❌ | Health check |
POST |
/leads |
❌ | Create a new lead (server-validated) |
GET |
/leads?search=&status= |
🔒 | List leads, filter by name/email search and status |
PATCH |
/leads/:id/status |
🔒 | Update a lead's status |
POST |
/auth/login |
❌ | Authenticate admin, starts session |
POST |
/auth/logout |
❌ | Destroys session |
GET |
/auth/me |
🔒 | Returns current session's admin info |
All protected routes return 401 Unauthorized if there's no valid session — the /admin frontend route redirects to /admin/login in that case.
- Sessions over JWT — session ID is stored server-side (MongoDB via
connect-mongo) and referenced by anhttpOnlycookie. This means logout is a real, immediate invalidation, not just "forget the token client-side." It also means the session token is never exposed to JavaScript, closing off a common XSS-based token theft vector. - Password hashing — all admin passwords are hashed with
bcryptbefore storage; the plaintext password only ever exists transiently during login comparison. - Server-side validation always runs, independent of the frontend — so a request sent directly via Postman/curl is validated exactly as strictly as one from the UI.
- CORS is locked to the deployed frontend origin.
- Node.js v18+
- A MongoDB connection string (local instance or MongoDB Atlas)
cd backend
npm install
cp .env.example .envFill in .env:
PORT=3000
MONGO_URI=mongodb+srv://<user>:<password>@cluster0.mongodb.net/leaddesk
SESSION_SECRET=replace-with-a-long-random-string
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=choose-a-strong-password
CLIENT_URL=http://localhost:5173Seed the admin user, then start the server:
npm run seed
npm run devAPI runs at http://localhost:3000.
cd frontend
npm install
cp .env.example .envFill in .env:
VITE_API_BASE_URL=http://localhost:3000/apinpm run devApp runs at http://localhost:5173.
- Visit the live landing page and submit a test lead — try submitting empty first to see validation in action.
- Visit the admin login and log in.
- Confirm your test lead appears, search for it by name or email, and toggle its status.
- Refresh the page to confirm the status change actually persisted to the database.
- Log out and confirm
/adminredirects back to the login screen.
Test credentials are provided separately in the submission (not committed to this public repo for obvious reasons).
- Glassmorphic dark UI — chosen to feel like a modern SaaS product rather than a generic bootstrapped form, while keeping contrast high enough for accessibility on both the hero copy and form inputs.
- Optimistic status updates — the admin dashboard updates the status badge immediately on click, then confirms with the server, so the interface feels instant rather than waiting on a network round-trip.
- Centralized API service layer (
services/api.js) — every request goes through one configured Axios instance instead of scatteredfetch()calls, so the base URL and credentials behavior are defined once. - Delete action on leads — added beyond the base spec to make the admin dashboard usable for real pipeline hygiene (clearing test/spam entries), while keeping status-toggle as the primary workflow action.
This project was built using Antigravity (AI coding tool) for scaffolding the initial React/Express structure, the Tailwind UI components, and the Mongoose schemas. After the initial generation, I reviewed and modified: [— fill in your specific changes here, e.g. "fixed session persistence across page refresh," "adjusted the color palette and spacing," "rewrote validation error copy," "added the delete-lead feature," "fixed a bug where status updates weren't saving to the DB" —]. I tested every flow manually end-to-end (form validation, auth, search, status persistence) in an incognito browser before deployment.
Built for the Digital Heroes Internship Task · digitalheroesco.com



