Skip to main content
RapidDev - Software Development Agency

How to Build Scheduling app with V0

Build a Calendly-style scheduling app with V0 using Next.js and Supabase that lets professionals set weekly availability, create custom event types, accept bookings from a public page, and sync with Google Calendar — all in about 1-2 hours without local setup.

What you'll build

  • Public booking page at /[username]/[slug] where guests select a date, pick an available time slot, and confirm
  • Dashboard with calendar view of upcoming bookings and booking management
  • Weekly availability editor with day-of-week toggles, start/end times, and timezone selection
  • Event type manager for configuring meeting durations, buffer times, and descriptions
  • Google Calendar OAuth integration that syncs bookings as calendar events
  • Available slot computation that subtracts existing bookings and buffer times from availability
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate11 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a Calendly-style scheduling app with V0 using Next.js and Supabase that lets professionals set weekly availability, create custom event types, accept bookings from a public page, and sync with Google Calendar — all in about 1-2 hours without local setup.

What you're building

Scheduling tools like Calendly solve a real friction point — the back-and-forth of finding a meeting time. A professional sets their availability, shares a booking link, and clients pick an open slot. The meeting appears on both calendars automatically.

V0 generates the booking page, availability editor, event type manager, and dashboard from prompts. The key technical challenge is computing available slots accurately. A server-side function generates all possible slots from the host's weekly availability, fetches existing bookings for the selected date range, subtracts conflicts including buffer minutes before and after each booking, and converts everything to the guest's timezone using date-fns-tz.

The architecture uses Next.js App Router with a public booking page (no auth required), authenticated dashboard pages, an API route for the Google Calendar OAuth callback, Server Actions for booking mutations, and an API route for slot computation that handles the timezone math server-side.

Final result

A scheduling app with a public booking page, weekly availability settings, configurable event types with buffer times, Google Calendar sync, and timezone-aware slot computation.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
Google Calendar APICalendar Sync
date-fns-tzTimezone Handling

Prerequisites

  • A V0 account (Premium recommended for the booking page and dashboard complexity)
  • A Supabase project (free tier works — connect via V0's Connect panel)
  • A Google Cloud project with Calendar API enabled and OAuth 2.0 credentials

Build steps

1

Set up the project and scheduling schema

Open V0 and create a new project. Use the Connect panel to add Supabase. Create the schema for availability, event types, bookings, and calendar connections.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a scheduling app. Create a Supabase schema with:
3// 1. availability: id (uuid PK), user_id (uuid FK), day_of_week (integer CHECK 0-6), start_time (time), end_time (time), timezone (text), created_at (timestamptz)
4// 2. event_types: id (uuid PK), user_id (uuid FK), name (text), slug (text UNIQUE), duration_minutes (integer), buffer_minutes (integer DEFAULT 0), color (text), description (text), is_active (boolean DEFAULT true), created_at (timestamptz)
5// 3. bookings: id (uuid PK), event_type_id (uuid FK), host_id (uuid FK), guest_name (text), guest_email (text), guest_phone (text), start_time (timestamptz), end_time (timestamptz), status (text CHECK confirmed/cancelled/completed/no_show), google_event_id (text), notes (text), created_at (timestamptz)
6// 4. calendar_connections: id (uuid PK), user_id (uuid FK), provider (text CHECK google/outlook), access_token (text), refresh_token (text), token_expires_at (timestamptz), created_at (timestamptz)
7// RLS: users manage own availability and event types. Bookings: host can read all, guests can insert (public booking page needs no auth for INSERT).
8// Generate SQL migration and TypeScript types.

Pro tip: The bookings table needs a special RLS policy: anyone can INSERT (for the public booking page) but only the host can SELECT and UPDATE. Use a policy like: CREATE POLICY "public_insert" ON bookings FOR INSERT WITH CHECK (true); CREATE POLICY "host_read" ON bookings FOR SELECT USING (host_id = auth.uid()).

Expected result: Supabase is connected with availability, event_types, bookings, and calendar_connections tables. RLS allows public booking inserts while restricting reads to the host.

2

Build the public booking page with slot computation

Create the public booking page where guests select a date, see available time slots, and book a meeting. The slot computation API subtracts existing bookings and buffer times from the host's availability.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a public booking page at app/[username]/[slug]/page.tsx.
3// This page requires NO authentication — guests book directly.
4// Requirements:
5// - Left side: event type Card showing name, duration, description, host name
6// - Right side: shadcn/ui Calendar for date selection
7// - Below calendar: available time slots as a grid of Buttons
8// - Slots come from GET /api/bookings/available-slots?event_type_id=X&date=YYYY-MM-DD&timezone=X
9// - On slot click: slide to confirmation form with guest_name Input, guest_email Input, guest_phone Input, notes Textarea
10// - "Confirm Booking" Button calls Server Action createBooking()
11// - After booking: success Card with date, time, and "Add to Calendar" link
12// - 'use client' for Calendar and slot selection
13//
14// Available slots API at app/api/bookings/available-slots/route.ts:
15// - Fetches host's availability for the selected day_of_week
16// - Generates all possible slots at duration_minutes intervals
17// - Fetches existing bookings for the date (include buffer_minutes before/after)
18// - Subtracts conflicts
19// - Converts remaining slots to guest's timezone using date-fns-tz
20// - Returns array of { start: string, end: string } in guest timezone
21//
22// Use shadcn/ui Calendar, Card, Button, Input, Textarea, Badge

Expected result: A public booking page with date selection, available time slot grid, guest information form, and confirmation. The API computes slots by subtracting existing bookings from availability.

3

Create the availability editor and event type manager

Build the dashboard pages for setting weekly availability (which days and times the host is available) and managing event types (meeting types with different durations).

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build availability and event type management pages.
3//
4// Availability page at app/dashboard/availability/page.tsx:
5// - 7 rows for each day of week (Sunday-Saturday)
6// - Each row: day name, Switch to enable/disable, start_time Select, end_time Select (15-min increments)
7// - Timezone Select at top (with common timezones: US/Eastern, US/Pacific, Europe/London, etc.)
8// - Server Action updateAvailability() saves all 7 days at once (delete + re-insert pattern)
9// - Visual weekly calendar preview showing enabled hours
10// - Use shadcn/ui Switch, Select, Card, Button
11//
12// Event types page at app/dashboard/event-types/page.tsx:
13// - Grid of event type Cards showing name, duration Badge, buffer info, color dot
14// - Switch on each Card to toggle is_active
15// - "Create Event Type" Button opens Dialog:
16// - name Input, slug Input (auto-generated from name), duration_minutes Select (15/30/45/60/90/120)
17// - buffer_minutes Select (0/5/10/15/30), color picker, description Textarea
18// - Each Card has booking link: copy to clipboard Button showing /[username]/[slug]
19// - Server Actions: createEventType(), updateEventType(), deleteEventType()
20// - Use shadcn/ui Card, Badge, Switch, Dialog, Input, Select, Textarea, Button, Toast

Expected result: An availability editor with day-of-week toggles and time range selectors, plus an event type manager with create/edit/toggle Cards and copyable booking links.

4

Add Google Calendar sync and booking dashboard

Build the Google Calendar OAuth integration that syncs bookings as calendar events, plus the dashboard showing upcoming bookings in a calendar view.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build Google Calendar integration and a booking dashboard.
3//
4// Google Calendar OAuth at app/api/calendar/google/callback/route.ts:
5// - Handles the OAuth callback: exchanges code for tokens
6// - Stores access_token, refresh_token, token_expires_at in calendar_connections
7// - Redirects back to dashboard with success Toast
8//
9// Calendar sync at app/api/calendar/sync/route.ts:
10// - Called after booking creation
11// - Fetches host's Google Calendar tokens from calendar_connections
12// - Refreshes token if expired
13// - Creates a Google Calendar event with booking details (guest name, email, time)
14// - Stores the google_event_id in the bookings row
15//
16// Dashboard at app/dashboard/page.tsx:
17// - Weekly calendar view showing all confirmed bookings as colored blocks
18// - Upcoming bookings list: Table with guest_name, event_type Badge, date, time, status Badge
19// - Booking actions: Cancel (Dialog with confirmation), Mark Complete, Mark No-Show
20// - "Connect Google Calendar" Button if no calendar_connections exist
21// - Links to Google OAuth URL with scope calendar.events
22// - Server Actions: cancelBooking(), updateBookingStatus()
23// - Use shadcn/ui Card, Table, Badge, Button, Dialog, Calendar, Toast
24//
25// Env vars in V0's Vars tab: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET (server-only, no NEXT_PUBLIC_)
26// NEXT_PUBLIC_APP_URL (needed for OAuth redirect URI and booking page links)

Pro tip: The Google Calendar OAuth redirect URI must point to your production URL (/api/calendar/google/callback). Set this in Google Cloud Console after your first Vercel deploy. During development, you can add http://localhost:3000 as an additional redirect URI.

Expected result: Google Calendar OAuth integration that syncs bookings as calendar events, plus a dashboard with weekly calendar view and booking management.

Complete code

app/api/bookings/available-slots/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3import { addMinutes, format, parse, isWithinInterval, startOfDay, endOfDay } from 'date-fns'
4import { toZonedTime, fromZonedTime } from 'date-fns-tz'
5
6const supabase = createClient(
7 process.env.SUPABASE_URL!,
8 process.env.SUPABASE_SERVICE_ROLE_KEY!
9)
10
11export async function GET(req: NextRequest) {
12 const { searchParams } = new URL(req.url)
13 const eventTypeId = searchParams.get('event_type_id')!
14 const dateStr = searchParams.get('date')!
15 const guestTz = searchParams.get('timezone') || 'America/New_York'
16
17 const { data: eventType } = await supabase
18 .from('event_types')
19 .select('*, availability:user_id(availability(*))')
20 .eq('id', eventTypeId)
21 .single()
22
23 if (!eventType) {
24 return NextResponse.json({ error: 'Event type not found' }, { status: 404 })
25 }
26
27 const hostTz = eventType.availability?.availability?.[0]?.timezone || 'UTC'
28 const date = new Date(dateStr)
29 const dayOfWeek = toZonedTime(date, hostTz).getDay()
30
31 const dayAvailability = (eventType.availability?.availability || []).find(
32 (a: { day_of_week: number }) => a.day_of_week === dayOfWeek
33 )
34
35 if (!dayAvailability) {
36 return NextResponse.json({ slots: [] })
37 }
38
39 const hostDate = format(toZonedTime(date, hostTz), 'yyyy-MM-dd')
40 const startDt = fromZonedTime(
41 parse(`${hostDate} ${dayAvailability.start_time}`, 'yyyy-MM-dd HH:mm:ss', new Date()),
42 hostTz
43 )
44 const endDt = fromZonedTime(
45 parse(`${hostDate} ${dayAvailability.end_time}`, 'yyyy-MM-dd HH:mm:ss', new Date()),
46 hostTz
47 )
48
49 const { data: existingBookings } = await supabase
50 .from('bookings')
51 .select('start_time, end_time')
52 .eq('host_id', eventType.user_id)
53 .eq('status', 'confirmed')
54 .gte('start_time', startOfDay(date).toISOString())
55 .lte('end_time', endOfDay(date).toISOString())
56
57 const buffer = eventType.buffer_minutes || 0
58 const duration = eventType.duration_minutes
59 const slots: { start: string; end: string }[] = []
60 let cursor = startDt
61
62 while (addMinutes(cursor, duration) <= endDt) {
63 const slotStart = cursor
64 const slotEnd = addMinutes(cursor, duration)
65 const bufferedStart = addMinutes(slotStart, -buffer)
66 const bufferedEnd = addMinutes(slotEnd, buffer)
67
68 const hasConflict = (existingBookings || []).some((booking) => {
69 const bStart = new Date(booking.start_time)
70 const bEnd = new Date(booking.end_time)
71 return bufferedStart < bEnd && bufferedEnd > bStart
72 })
73
74 if (!hasConflict && slotStart > new Date()) {
75 const guestStart = toZonedTime(slotStart, guestTz)
76 const guestEnd = toZonedTime(slotEnd, guestTz)
77 slots.push({
78 start: format(guestStart, "HH:mm"),
79 end: format(guestEnd, "HH:mm"),
80 })
81 }
82
83 cursor = addMinutes(cursor, duration)
84 }
85
86 return NextResponse.json({ slots })
87}

Customization ideas

Add email confirmations with Resend

Send booking confirmation emails to both the host and guest with meeting details, calendar invite (.ics attachment), and a cancellation link.

Build recurring availability exceptions

Let hosts block specific dates (vacations, holidays) that override the regular weekly availability, stored in a date_overrides table.

Add team scheduling

Extend to team use where a booking request is routed to the first available team member using round-robin or least-recently-booked logic.

Build payment collection

Integrate Stripe to charge for appointments at booking time, with automatic refunds on host-initiated cancellations.

Common pitfalls

Pitfall: Computing available slots on the client instead of the server

How to avoid: Use an API route (app/api/bookings/available-slots/route.ts) that computes slots server-side with the service role key, returning only the final available slots to the client.

Pitfall: Not accounting for buffer minutes when checking for conflicts

How to avoid: Expand each existing booking's time range by buffer_minutes on both sides when checking for conflicts. A 10-minute buffer means a meeting ending at 2:00 blocks slots until 2:10.

Pitfall: Storing times in the host's local timezone instead of UTC

How to avoid: Store all booking times as timestamptz (UTC). The availability table stores time-of-day in the host's timezone for weekly scheduling, but all bookings and comparisons use UTC. Convert to guest timezone only for display using date-fns-tz.

Best practices

  • Compute available slots server-side in an API route — never expose the host's full calendar to the client
  • Store booking times as timestamptz (UTC) and use date-fns-tz for timezone conversions on display only
  • Account for buffer_minutes on both sides of existing bookings when computing available slots
  • Store GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in V0's Vars tab without NEXT_PUBLIC_ prefix — they are server-only
  • Use V0's Design Mode (Option+D) to adjust the booking page layout and Calendar styling without spending credits
  • Allow public INSERT on the bookings table via RLS while restricting SELECT to the host for the public booking flow

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a scheduling app with Next.js and Supabase. Write a function that computes available time slots for a given date. It takes the host's weekly availability (day_of_week, start_time, end_time, timezone), event duration in minutes, buffer minutes, and an array of existing bookings (start_time, end_time as UTC timestamps). It should generate all possible slots, subtract conflicts including buffer time, filter out past slots, and return the remaining slots converted to a given guest timezone using date-fns-tz.

Build Prompt

Create a time slot picker component. Accept an array of available slots (start: string, end: string) and a selected slot state. Render slots as a responsive grid of Buttons. The selected slot should have a ring and primary variant. Show a 'No slots available' message when the array is empty. Use Skeleton components while loading. Mark as 'use client' for the interactive selection state.

Frequently asked questions

Can I build this on V0's free tier?

V0 Free provides enough credits for the basic booking page and dashboard. Premium ($20/month) is recommended because this project has a public booking page, availability editor, event type manager, and calendar integration that benefit from prompt queuing.

How does the slot computation handle timezones?

The host sets availability in their local timezone. The API route generates slots in the host's timezone, checks conflicts in UTC, then converts the remaining available slots to the guest's timezone using date-fns-tz. All bookings are stored as UTC timestamptz.

Do guests need to create an account to book?

No. The public booking page at /[username]/[slug] requires no authentication. Guests enter their name, email, and optionally phone number. The bookings table has an RLS policy that allows public INSERT while restricting reads to the host.

How does Google Calendar sync work?

The host connects their Google account via OAuth. When a booking is confirmed, the system creates a Google Calendar event using the Calendar API with the booking details. The google_event_id is stored for future updates or cancellations.

How do I deploy the scheduling app?

Click Share then Publish to Production in V0. After deploying, update the Google Cloud Console OAuth redirect URI to https://yourdomain.vercel.app/api/calendar/google/callback. Set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET (server-only), and NEXT_PUBLIC_APP_URL in V0's Vars tab.

Can RapidDev help build a custom scheduling platform?

Yes. RapidDev has built 600+ apps including scheduling platforms with team round-robin, payment collection, and multi-calendar sync. Book a free consultation to discuss your requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.