A production-grade, full-stack scheduling platform inspired by Cal.com. Built from scratch with Next.js 14, Prisma, Tailwind CSS, and Clerk. Features real-time availability, cross-timezone booking, automated email notifications, and robust double-booking prevention.
- Create events with custom durations (15 / 30 / 60 / 90 min), unique URL slugs, buffer times, and custom booking questions
- Timezone-aware slot generation — filters past times and accounts for buffer periods
- Cross-event-type double-booking prevention — conflict detection spans all of a host's event types, not just the one being booked
- Full booking lifecycle: confirm → reschedule → cancel with status tracking and chain references
- Auto-detects the invitee's local timezone via the browser
- Slots rendered in the invitee's timezone, stored in the host's timezone
- Past slots on the current day are automatically hidden
- Clerk sign-up / sign-in with automatic database user provisioning
- Demo Mode — unauthenticated visitors get a fully seeded account with sample events, bookings, and availability
- Edit / delete / create actions are gated behind auth with informative sign-in prompts
- Booking confirmation → invitee
- New booking alert → host
- Cancellation notice → invitee & host
- Reschedule notice → invitee & host
- Automated 24-hour reminders via Vercel Cron (runs daily at 9 AM UTC)
- Serverless-safe delivery using
Promise.allSettledand thesafeSendwrapper
- Dark theme with Vercel/Apple-inspired
#111111design language - Mobile-first responsive layout with slide-out sidebar
- Micro-animations, hover effects, and smooth transitions
- Premium feature lock pages (Teams, Routing, Workflows)
| Category | Technology | Purpose |
|---|---|---|
| Framework | Next.js 14 (App Router) | Server components, API routes, middleware |
| Language | TypeScript | End-to-end type safety |
| Styling | Tailwind CSS 3.4 | Utility-first responsive design |
| Database | PostgreSQL (Neon / Render) | Serverless-compatible relational DB |
| ORM | Prisma 5 | Type-safe queries and migrations |
| Auth | Clerk | OAuth, email/password, sessions |
| Nodemailer (Gmail SMTP) | Transactional email delivery | |
| Icons | Lucide React | Consistent icon system |
| Hosting | Vercel | Serverless deploy, edge middleware, cron |
cal-clone/
├── prisma/
│ ├── schema.prisma # 8 database models
│ └── seed.js # Seeder script
│
├── src/
│ ├── app/
│ │ ├── (admin)/ # Dashboard (route group)
│ │ │ ├── layout.tsx # Sidebar + Navbar shell
│ │ │ ├── event-types/ # Event type CRUD
│ │ │ ├── bookings/ # Upcoming / Past / Cancelled
│ │ │ ├── availability/ # Weekly rules + date overrides
│ │ │ ├── teams/ # Premium lock page
│ │ │ ├── routing/ # Premium lock page
│ │ │ └── workflows/ # Premium lock page
│ │ │
│ │ ├── api/
│ │ │ ├── bookings/ # Create + list bookings
│ │ │ │ └── [id]/ # Get, cancel, reschedule
│ │ │ ├── event-types/ # CRUD + public slug lookup
│ │ │ ├── slots/ # Available slots (tz-aware)
│ │ │ ├── schedules/ # Availability schedules
│ │ │ ├── availability/ # Availability rules
│ │ │ ├── date-overrides/ # Date-specific overrides
│ │ │ ├── cron/reminders/ # Vercel Cron: 24h reminders
│ │ │ └── auth/ # Clerk auth adapter
│ │ │
│ │ ├── book/[slug]/ # Public booking flow
│ │ │ ├── page.tsx # Calendar + time picker
│ │ │ └── confirmed/ # Confirmation page
│ │ │
│ │ ├── login/ # Custom login
│ │ ├── sign-in/ & sign-up/ # Clerk auth pages
│ │ ├── pricing/ # Pricing tiers
│ │ └── page.tsx # Landing page
│ │
│ ├── components/
│ │ ├── admin/ # Sidebar, EventTypeForm, UpgradeView
│ │ ├── booking/ # CalendarPicker, TimeSlots, BookingForm
│ │ └── Navbar.tsx # Top navigation
│ │
│ └── lib/
│ ├── auth.ts # Clerk + demo user logic
│ ├── email.ts # Nodemailer + 6 HTML templates
│ ├── prisma.ts # Prisma singleton
│ └── slots.ts # Slot math & overlap detection
│
├── vercel.json # Cron config
└── package.json
The email system in src/lib/email.ts is built for serverless (Vercel):
Email Types:
| Event | To Invitee | To Host |
|---|---|---|
| New booking | ✅ Confirmation | ✅ New booking alert |
| Cancellation | ✅ Cancellation notice | ✅ Cancellation alert |
| Reschedule | ✅ New time details | ✅ Reschedule alert |
| 24h Reminder | ✅ Upcoming reminder | ✅ Upcoming reminder |
Design Decisions:
safeSend()wrapper — catches errors so a failed email never crashes the booking flowPromise.allSettled()— all emails are awaited before the HTTP response, critical for Vercel which kills pending promises after responding- Extended timeouts (10s connect, 10s greeting, 15s socket) for serverless cold starts
- Graceful degradation — missing SMTP config logs a warning instead of throwing
- Inline HTML templates — zero external dependencies for maximum compatibility
Cron Reminders:
Configured in vercel.json to run daily at 09:00 UTC. The job finds confirmed bookings within the next 24 hours, sends reminders to both parties, and marks reminderSent = true to prevent duplicates.
Conflicts are blocked at three layers:
| Layer | Endpoint | How It Works |
|---|---|---|
| Slot Display | GET /api/slots |
Queries all confirmed bookings across every event type for the host, removes occupied slots from the UI |
| Booking Creation | POST /api/bookings |
Server-side overlap check against all host bookings — returns 409 Conflict if occupied |
| Reschedule | PATCH /api/bookings/[id] |
Same cross-event-type check, excluding the booking being rescheduled |
Overlap algorithm: Half-open interval math — [A_start, A_end) ∩ [B_start, B_end) ≠ ∅
8 models defined in prisma/schema.prisma:
| Model | Purpose | Key Fields |
|---|---|---|
| User | Host accounts | clerkId, name, email, timezone |
| EventType | Bookable event templates | title, slug, durationMinutes, bufferMinutes, isActive |
| Booking | Scheduled appointments | date, startTime, endTime, status, reminderSent |
| BookingQuestion | Custom questions per event type | label, required, order |
| BookingAnswer | Invitee responses to questions | bookingId, questionId, answer |
| AvailabilitySchedule | Named schedule per user | name, timezone, isDefault |
| AvailabilityRule | Weekly recurring rules | dayOfWeek, startTime, endTime |
| DateOverride | Date-specific exceptions | date, startTime, endTime, isBlocked |
Relationships:
User→ has manyEventTypeandAvailabilityScheduleEventType→ has manyBookingandBookingQuestionBooking→ has manyBookingAnswer; self-references viarescheduledFromIdAvailabilitySchedule→ has manyAvailabilityRuleandDateOverride
- Node.js 18+
- PostgreSQL — free tier on Neon or Render
- Clerk account — clerk.com
- Gmail App Password — Google Guide
# 1. Clone
git clone https://github.com/Akshay000000/Cal.com_Clone.git
cd Cal.com_Clone
# 2. Install
npm install
# 3. Configure .env (see below)
# 4. Push database schema
npx prisma db push
# 5. Run
npm run devCreate a .env file in the project root:
# Database
DATABASE_URL="postgresql://user:pass@host:5432/dbname"
# Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..."
CLERK_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/event-types
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/event-types
# Email (Gmail SMTP)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_gmail_app_password
SMTP_FROM="Cal Clone <your_email@gmail.com>"
# App
NEXT_PUBLIC_BASE_URL=http://localhost:3000This app is optimized for Vercel:
- Push to GitHub → import in Vercel
- Add all
.envvariables in Settings → Environment Variables - Set
NEXT_PUBLIC_BASE_URLto your production domain - The cron job in
vercel.jsonauto-schedules daily reminders at 9 AM UTC
Open source under the MIT License.
Built by Akshay ✦