DEV Community

Cover image for πŸ§‘β€πŸ’» How I Built the World's Best NextJS, AI Scheduling App πŸ€–βœ¨
Arindam Majumder Subscriber for CopilotKit

Posted on

πŸ§‘β€πŸ’» How I Built the World's Best NextJS, AI Scheduling App πŸ€–βœ¨

TL;DR

By the end of this article, you'll learn how to:

  • Integrate an AI copilot into your Next.js application by building a scheduling app with Cal.com.

  • Develop and manage a custom scheduler that enhances user experience across your application.

Plus, you'll have a cool project to showcase in your portfolio!

Cool


What is an AI Copilot?

An AI Copilot, is an in-app AI assistant that helps users answer questions and take actions inside an application. It brings LLM intelligence right into your application.

The following are some of the potential use cases for AI copilots:

  • ChatBot: A contextual in-app messaging facility for the application that can help the users with queries and perform some specific actions in the application.
  • AI Autocomplete: Smart input controls that offer suggestions related to the context as the user is typing in a message.
  • CoAgents: Artificially intelligent helpers who can work hands-on with your app and your users, capable of performing complex tasks on their own.

CopilotKit is the leading, most robust, and easiest to use open-source framework for building in-app AI copilots. You can have a fully custom AI copilot running in your app within minutes.

Copilot

Checkout CopilotKit ⭐️


Prerequisites

To follow along with this tutorial, you need to have the following.

Here is a demo of what we’ll be building throughout this tutorial:

Demo1

Creating a New Next.js Project

Next.js is one of the most widely used frameworks for creating scalable and high-performance web applications. It builds upon React's core features and offers server-side rendering, static site generation, and simpler routing interfaces which help to create fast and production-ready websites with good SEO.

To allow us to focus on learning how to integrate Cal.com and Copilot into your room booking Next.js app, I have created the components and UI interface for this app. Run the command below to clone and run the app:

git clone https://github.com/icode247/copilot-booking-app 
&& cd copilot-booking-app && npm install && npm run dev
Enter fullscreen mode Exit fullscreen mode

ScreenShot-1

Integrating Cal.com API for Booking

Now define those routes. In those routes, we’ll make API requests to the Cal.com APU to create new bookings or cancel bookings. In your API folder, create a new bookings/route.ts file and add the code below:

import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';

export async function POST(request: NextRequest) {
  const { room, start, end, time, email } = await request.json();

  try {
    const [startDate] = start.split("T");
    const startDateTime = new Date(`${startDate}T${time}:00`);
    const formattedStart = startDateTime.toISOString();

    const endDateTime = new Date(startDateTime);
    endDateTime.setHours(endDateTime.getHours() + 1);
    const formattedEnd = endDateTime.toISOString();

    // Step 1: Create event-type
    const eventTypePayload = {
      length: 60,
      slug: `booking-${Date.now()}-${room.toLowerCase().replace(/\s+/g, "-")}`,
      title: `Booking for ${room}`,
      description: `Booking for ${room} from ${formattedStart} to ${formattedEnd}`,
      locations: [{ type: "inPerson", address: room }],
      disableGuests: false,
      slotInterval: 0,
      minimumBookingNotice: 0,
      beforeEventBuffer: 0,
      afterEventBuffer: 0,
    };

    const eventTypeResponse = await axios.post(
      `${CALCOM_API_BASE_URL}/event-types`,
      eventTypePayload,
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.NEXT_PUBLIC_CALCOM_API_KEY}`,
        },
      }
    );

    const eventTypeId = eventTypeResponse.data.data.id;

    // Step 2: Create booking
    const bookingPayload = {
      end: formattedEnd,
      start: formattedStart,
      eventTypeId,
      eventTypeSlug: eventTypePayload.slug,
      timeZone: "Africa/Lagos",
      user: [email],
      language: "en",
      bookingUid: `booking-${Date.now()}`,
      metadata: {},
      responses: {
        name: email.split("@")[0],
        email,
        guests: [],
        notes: `Booking for ${room} from ${formattedStart} to ${formattedEnd}`,
      },
    };

    const bookingResponse = await axios.post(
      `${process.env.CALCOM_API_BASE_URL}/bookings`,
      bookingPayload,
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.NEXT_PUBLIC_CALCOM_API_KEY}`,
        },
      }
    );

    return NextResponse.json({ booking: bookingResponse.data }, { status: 201 });
  } catch (error) {
    console.error("Error response status:", error.response?.status);
    return NextResponse.json(
      {
        error: "Failed to create booking",
        details: error.response?.data || error.message,
      },
      { status: 500 }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code will create an event type in Cal.com with the booking details, including the room and time information. Then, using the ID of the newly created event type, it proceeds to create an actual booking on Cal.com. The code handles date formatting constructs the necessary payloads for both API calls, and uses axios to send requests to the Cal.com API.

Create a new cancel/route.tsfile and add the code below:

import { NextRequest, NextResponse } from "next/server";
import axios from "axios";

export async function POST(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const body = await request.json();
    const bookingId = params.id;

    const cancelledBookingResponse = await axios.post(
      `${process.env.CALCOM_API_BASE_URL}/bookings/${bookingId}/cancel`,
      body,
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.NEXT_PUBLIC_CALCOM_API_KEY}`,
        },
      }
    );

    return NextResponse.json(
      { booking: cancelledBookingResponse.data },
      { status: 201 }
    );
  } catch (error) {
    console.error("Error cancelling booking:", error);
    return NextResponse.json(
      { error: "Failed to cancel booking" },
      { status: 500 }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code implements an API route handler for canceling bookings using the Cal.com API.

Now update your app/page.tsx file to use the RoomBookingProvider we created:

// app/page.tsx
'use client';

import Layout from "@/components/Layout";
import RoomBookingCard from "@/components/RoomBookingCard";
import { RoomBookingProvider } from "@/lib/hooks/use-room-booking";

export default function Home() {
  return (
    <RoomBookingProvider>
      <Layout>
        <RoomBookingCard />
      </Layout>
    </RoomBookingProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then, update your RoomBookingCard.tsx file to use the useRoomBooking hook to allow users to book rooms.

// components/RoomBookingCard.tsx
"use client";

import { FC, useEffect, useState } from "react";
import RoomList from "./RoomList";
import Notification from "./Notification";
import BookingForm from "./BookingForm";
import { useRoomBooking } from "@/lib/hooks/use-room-booking";

const RoomBookingCard: FC = () => {
  const [selectedRoom, setSelectedRoom] = useState<string | null>(null);
  const [notification, setNotification] = useState<string | null>(null);
  const [rooms, setRooms] = useState([]);
  const { addBooking } = useRoomBooking();

  useEffect(() => {
    async function fetchRooms() {
      const response = await fetch("/api/rooms");
      const data = await response.json();
      setRooms(data);
    }
    fetchRooms();
  }, []);

  const handleRoomSelect = (room: string) => {
    setSelectedRoom(room);
  };

  const handleBookingConfirm = async (
    sdate: string,
    time: string,
    edate: string,
    email: string
  ) => {
    try {
      if (selectedRoom) {
        await addBooking(selectedRoom, sdate, time, edate, email);
        setNotification("Booking confirmed!");
        setSelectedRoom(null);
      }
    } catch (error) {
      setNotification(error.message);
    }
  };

  return (
    <div>
      {notification && (
        <Notification
          message={notification}
          onClose={() => setNotification(null)}
        />
      )}
      {selectedRoom ? (
        <BookingForm room={selectedRoom} onConfirm={handleBookingConfirm} />
      ) : (
        <RoomList rooms={rooms} onSelectRoom={handleRoomSelect} />
      )}
    </div>
  );
};

export default RoomBookingCard;
Enter fullscreen mode Exit fullscreen mode

Now you can select any room, enter your details, and book it.

ScreenShot-2

Now let’s make the application more interesting by adding AI copilot using CopilotKit. First, visit theΒ OpenAI Developers' PlatformΒ and create a new secret key.

Integrating Copilotkit for AI-Driven Interaction

OpenAI dashboard

Setting Up Copilotkit

Copilotkit offers two options for integration:

  • Copilot Cloud: Which easiest way to get started with CopilotKit and
  • Self Hosting: This sets up an instance of Copilot Runtime on your own infrastructure.

For this tutorial, we’ll use the Copilot Cloud. Click here to get your Copilot Cloud API key for free. Then, replaceΒ <your-public-api-key>Β with your actual Open AI key to generate a Copilot public key.

Add your API key securely to your .env.local file:

PUBLIC_API_KEY=<your-public-api-key>
Enter fullscreen mode Exit fullscreen mode

Update your app/page.tsx file to wrap theΒ <CopilotKit>Β provider in your app:


// app/page.tsx
"use client";

import Layout from "@/components/Layout";
import RoomBookingCard from "@/components/RoomBookingCard";
import { RoomBookingProvider } from "@/lib/hooks/use-room-booking";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";

export default function Home() {
  return (
    <CopilotKit publicApiKey={process.env.PUBLIC_API_KEY}>
      <RoomBookingProvider>
        <Layout>
          <RoomBookingCard />
        </Layout>
      </RoomBookingProvider>
      <CopilotPopup />
    </CopilotKit>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the above code, we imported the <CopilotPopup /> component and wrapped the page with the <CopilotKit> provider, both from @copilotkit/react-ui. We also imported optional built-in styles from the same package to enhance the UI.

Now you’ll see the chat popup in the bottom right corner of the page.

Popup Screenshot

For Copilot to provide us with the right answers and perform tasks, we need to make it aware of our app state using theΒ useCopilotReadableΒ hook. Update the code in your lib/hooks/use-room-booking.tsx file to provide copilot with the state of our bookings and rooms:

// ...

import { useCopilotReadable } from "@copilotkit/react-core";
// ...

export function RoomBookingProvider({ children }: { children: ReactNode }) {
    // ... 
    const [rooms, setRooms] = useState([]);

    // ... 

    useCopilotReadable({
       description: "The state of the booking list",
       value: JSON.stringify(bookings),
    });

    useCopilotReadable({
       description: "The state of the rooms list",
       value: JSON.stringify(rooms),
    });
    //...
}
Enter fullscreen mode Exit fullscreen mode

Now you can test the Copilot by asking how many bookings you have.

CopilotKit Chat screen

Handling Booking and Cancellations

Let’s enable the Copilot to perform more tasks like creating bookings, canceling bookings, and having information about available rooms. To achieve that, we’ll use the Copilot useCopilotActionΒ hook. This hook allows you to make actions available to the copilot.

Update your lib/hooks/use-room-booking.tsx file and add the following actions:

// ...

export function RoomBookingProvider({ children }: { children: ReactNode }) {
// ...

  useCopilotAction({
    name: "addBooking",
    description: "Adds a booking to the list",
    parameters: [
      {
        name: "room",
        type: "string",
        description: "The room to be booked",
        required: true,
      },
      {
        name: "date",
        type: "string",
        description: "The date of the booking",
        required: true,
      },
      {
        name: "time",
        type: "string",
        description: "The time of the booking",
        required: true,
      },
      {
        name: "end",
        type: "string",
        description: "The checkout time for booking",
        required: true,
      },
      {
        name: "email",
        type: "string",
        description: "Email address of the user booking the room",
        required: true,
      },
    ],
    handler: async ({ room, date, time, end, email }) => {
      await addBooking(room, date, time, end, email);
    },
  });

  useCopilotAction({
    name: "cancelBooking",
    description: "Cancels a booking from the list",
    parameters: [
      { name: "room", type: "string", description: "The room of the booking to be cancelled", required: true },
      { name: "date", type: "string", description: "The date of the booking to be cancelled", required: true },
      { name: "reason", type: "string", description: "The reason for cancellation", required: true },
    ],
    handler: async ({ room, date, reason }) => {
      await cancelBooking(room, date, reason);
    },
  });

  useCopilotAction({
    name: "fetchAvailableRooms",
    description: "Fetches available rooms for a given date",
    parameters: [
      {
        name: "date",
        type: "string",
        description: "The date to check room availability",
        required: true,
      },
    ],
    handler: async ({ date }) => {
      const availableRooms = await fetchAvailableRooms(date);
      setRooms(availableRooms);
    },
  });

  useCopilotAction({
    name: "setBookingStatus",
    description: "Sets the status of a booking",
    parameters: [
      {
        name: "id",
        type: "number",
        description: "The ID of the booking",
        required: true,
      },
      {
        name: "status",
        type: "string",
        description: "The status of the booking",
        enum: Object.values(BookingStatus),
        required: true,
      },
    ],
    handler: ({ id, status }) => {
      setBookingStatus(id, status);
    },
  });
  //...
 }
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, we used the useCopilotActionΒ hook to create actions to create bookings, addBooking, cancelBooking, fetchAvailableRooms, and setBookingStatus. Each of these actions takes an object with the following properties.

name stands for the name of the action.

description refers to characterizing the action. This is very important because it would allow our copilot to pick the right action.

parameters is the set of parameters that the action takes which is an array. It conforms to the JSON Schema format.

handler is simply a function that will be executed when the action is executed.

Now you can use the copilot to perform advanced actions like seeing a list of available rooms, booking, and canceling bookings. Feel free to chat with the copilot to perform other actions.

Advance Actions with CopilotKit

Excellent, you now have an AI-powered scheduling app to add to your portfolio.


Wrapping It Up

CopilotKit is an innovative and convenient tool for adding AI capabilities to your products. Whether it is creating a voice interactive system or deploying automation for challenging procedures, its adaptability will satisfy the needs of any software developer who wants to incorporate AI into his work.

If you are developing products with a strong emphasis on AI or are looking for ways to add it to the applications you have made, then you should definitely consider using CopilotKit. The interface is simple and can be configured quickly which translates into reduced time as well as improved focus globally.

Your curiosity about tools such as CopilotKit helps you keep up with the rapidly changing landscape of the software industry.

Join CopilotKit's amazing community of developers and come build with us!

Don't forget to check out our GitHub and show your love by giving us a star ⭐️

Top comments (20)

Collapse
 
srbhr profile image
Saurabh Rai

Dad-bod Pikachu is πŸ”₯

Collapse
 
arindam_1729 profile image
Arindam Majumder

HahaπŸ”₯πŸ”₯

Collapse
 
morgan-123 profile image
Morgan

πŸ˜†

Collapse
 
nevodavid profile image
Nevo David

Awesome article!

Collapse
 
arindam_1729 profile image
Arindam Majumder

Thanks, Nevo!

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

I use Cal.com for my scheduling and this is a great use case to boost it with AI.
Nicely done @arindam_1729!

Collapse
 
arindam_1729 profile image
Arindam Majumder

Glad you liked it, Nathan!

With CopilotKit, We can make so many cool projects that will help us save our time!

Collapse
 
ddebajyati profile image
Debajyati Dey

Damn! Dope content!

Collapse
 
johncook1122 profile image
John Cook

I need to look further into Cal.com, but as far as building an AI scheduling app, are time zones automatically handled?

I'm curious because I'm building a SaaS, and I heard about CopilotKit a few days ago and was intrigued. Just now looking into them.

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Hey @johncook1122, thanks for checking out CopilotKit. Getting started is extremely easy and depending on which kind of AI interface you want to add, we have a suite of options.
docs.copilotkit.ai/

Collapse
 
arshadayvid profile image
David Asaolu

Nicely written! πŸ”₯
Great tutorial

Collapse
 
mathew00112 profile image
Mathew

I only use open source so when I see a tutorial like this, it sparks my interest to build it.
I mean why not, I already use CAL :)

Collapse
 
sree0_0 profile image
SREETAMA

Amazing article πŸ‘

Collapse
 
astrodevil profile image
Astrodevil

Very impressive article!

Collapse
 
david-723 profile image
David

This is sick!