How to Build a Calendly Clone in 30 Minutes
Tags: googlecloud, opensource, codenewbie, beginners
Published: 2021-03-23 14:00:28

By [Nikhil Nandagopal](https://twitter.com/nikilnandagopal) and [Vihar Kurama](https://twitter.com/vihar13k)

We've been a long-time _Calendly_ user, an app that lets others block our time and schedules meetings. And we love how much time it saves me. But we've needed a few additional features that were only available on their premium plans. Instead of upgrading it, we've decided to build our own that can be easy to use and fully customisable!

![Calendly Clone on Appsmith](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezkj5axmhdrojehd0vnq.png)

Unfortunately, there's one problem here. We'll have to pick technologies, write lots of code, build UI, deploy them from scratch. Which is again time-consuming. To skip all of these, we used [Appsmith](https://www.appsmith.com/) for the UI, APIs by Google Calendar and Zoom for video-call services.

> ✨ Appsmith is a cloud or self-hosted open-source platform to build admin panels, CRUD apps and workflows. With Appsmith, you can build everything you need 10x faster. ✨ 

You can build this, too, in just under 30 minutes! Click [here](https://app.appsmith.com/applications/600186605b452f525458d6d2/pages/600186605b452f525458d6d4) to try. Block the time for a live demo. Here's a quick sketch of what we'll be going through in this guide: 

- Connecting Your Google Calendar
- Listing our Events from Google Calendar
- Displaying Free Slots
- Creating Calendar Events
- Setting up Zoom for Video Conferencing
- Final step

Let’s get started!

## Connecting Your Google Calendar

Our first task is to connect our Google Calendar and list down all our calendar events. For this, we’ll have to use Google APIs and OAuth authorization to authenticate the user from Appsmith.  

If you're an existing user, you can sign in to [Appsmith](https://app.appsmith.com/applications) or sign up for a new one (it's free!). We’ll walk through different steps to list our events!

1. First, you’ll have to create a new application on Appsmith.
2. A new application opens up an application titled Untitled Application 1; you can rename it by double-clicking on the existing one.
3. Next, you’ll have to create a new data-source to interact with Google Calendar: To do this create a new API by clicking on the + icon on the left navigation.
4. Add a new API and save it as a data source with the following URL:
`https://www.googleapis.com/calendar`
5. You can also set the name of the data source; in this case, we’ll call it GCalender.
6. Now, navigate to the GCalender data source and set the following configuration:
`Authentication Type`: `Oauth 2.0`
`Grant Type`: `Authorization Code`
7. Add Access Token URL: `https://oauth2.googleapis.com/token` this token allows users to verify their identity, and in return, receive a unique access token in return.
8. Add the Client ID and Client Secret from Google Cloud Platform
9. Lastly, set the following config:
- Scope: `https://www.googleapis.com/auth/calendar`
- Authorisation URL https://accounts.google.com/o/oauth2/v2/auth
**Add Auth params**
`prompt`: `consent`, 
`access_type`: `offline`
10. Save and Authorize the first time around!

Awesome! We've now had the authorisation to access our google calendar. 

## Listing our Events from Google Calendar

Our next step is to create an Appsmith App and fetch all the calendar events.

Let’s create an API to fetch events from our calendar and call it `fetch_calendar_events`. Use the following CURL command:


```curl
curl --location --request GET 'https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=%3CMIN_TIME%3E&timeMax=%3CMAX_TIME%3E&singleEvents=true&orderBy=startTime'
```


Let's make these parameters dynamic by adding some javascript. To do this, we'll use Javascript and **moment.js**, both of which can be used in Appsmith by writing `{{ }}` in any field. 

```js
// Replace MIN_TIME 
{{moment().format("YYYY-MM-DDT00:mm:ss+05:30")}} 

// Replace MAX_TIME
{{moment().add(1,"days").format("YYYY-MM-DDT00:mm:ss+05:30")}} 
```

Now replace the placeholders with the above js snippets and hit run to fetch your calendar events! Below is a screenshot explaining the same. 

![Updating Params](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fum9g87an0dv7g014ta0.png)

Awesome! This will now display all your calendar events.

## Displaying Free Slots

Now that we have all the events from our calendars, we need to build a UI to display these events. Appsmith has a simply _DnD_ interface to build UI. We can use the **Table** to display our events, a **date picker** to select which day we should fetch events for, and a **dropdown** to select the meeting duration we'd like our users to schedule. My UI looks like this, but you can get creative and built something that feels intuitive to you. Be sure to name your widgets well. The naming I'm following in this post is

- `durationDropdown` for the dropdown widget
- `slotDatePicker` for the date picker widget 
- `eventsTable` for the table widget

With an idea of the layout and inputs we need, we can configure our Table to display the events our API is returning using JS. Now, set the table data to : `{{fetch_calendar_events.data.items}}`

Now, this is how our app should look like:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ejasvzba07sfk1w7xvq.png)

No surprise our table doesn't look very readable because APIs are rarely built to work with views out of the box! Now with a little Javascript, we can transform the ugly API response into something human-readable and add some logic to show us the free slots instead of the calendar event. The following code goes into the table data property of the `eventsTable`.

```js
{{
function() {
let bookings = fetch_calendar_events.data.items;
let bookingIndex = 0;
const startHour = 10;
const endHour = 20;
let startingTime = moment(slotDatePicker.selectedDate, "DD/MM/YYYY");
startingTime.hour(startHour);
startingTime.minutes(0);
const hr = startingTime.hour();
let slots = [];
const slotDuration = Number(durationDropdown.selectedOptionValue);
while (startingTime.hour() < endHour) {
  if (startingTime.isBefore(moment())) {
    startingTime = moment();
    if (startingTime.minutes() > 30) {
      startingTime.minutes(0);
      startingTime.hour(startingTime.hour() + 1);
    } else startingTime.minutes(30);
  }
  const booking = bookings[bookingIndex];
  let bookingStart = undefined;
  if (booking) {
    bookingStart = moment(booking.start.dateTime);
  } else {
    bookingStart = moment(DatePicker1.selectedDate, "DD/MM/YYYY");
    bookingStart.hour(endHour);
    bookingStart.minutes(0);
  }
  const slotNum = Math.floor(Math.round(moment.duration(bookingStart.diff(startingTime)).asMinutes()) / slotDuration);
  for (let i = 0; i < slotNum; i++) {
    const slotStartTime = startingTime.format("HH:mm");
    startingTime.add(slotDuration, "minutes");
    const slotEndTime = startingTime.format("HH:mm");
    slots.push({
      startTime: slotStartTime,
      endTime: slotEndTime
    });
  }
  startingTime = booking ? moment(booking.end.dateTime) : startingTime.hour(endHour);
  bookingIndex += 1;
}
return slots.map((slot) => {
  return {
    slot: slot.startTime + " - " + slot.endTime
  }
})
}()
}}
```

The above logic was really the hardest part of building this application and took the longest to get right (but also the most fun part). The code declares a self-invoking function that iterates over the response of the `fetch_calendar_events` API and adds free time slots to array slots that are returned by the function. It begins iterating from 10 am to 8 pm (again hardcoding my workday for convenience) and uses the value in the `durationDropdown` to determine the number of free slots of selected duration that can fit between the current available time and the next calendar event. It then skips to the end of the next busy calendar event and continues this till it reaches the end of my workday!

## Creating Calendar Events

To schedule a meeting, we can create a button on the table and configure it to open a modal once it is clicked. The modal UI can also be custom-built using DnD to capture a user's name, email, and purpose of the meeting.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k7pcrmuex1o9i0tnhndc.gif" width="100%" alt="creating-cal-envents" />

Name the inputs as `nameInput`, `emailInput` and `purposeInput` so we're able to use them in our API. Let's import the following CURL and name the API create_event:

```curl
curl --location --request POST 'https://www.googleapis.com/calendar/v3/calendars/primary/events?text=%3CUSER_NAME%3E%20Meeting&sendUpdates=all' \
--header 'Content-Type: text/plain' \
--data-raw '{
    "start": {
        "dateTime": "<START_TIME>"
    },
    "end": {
        "dateTime": "<END_TIME>"
    },
    "description": "<DESCRIPTION>",
    "summary": "<TITLE>",
    "attendees": [{ "email": "<EMAIL>" }]
}'
```

In the above API, we need to accept dynamic values from our app, so we need to replace some of the params and headers. 

_USER_NAME:_
```js
{{nameInput.text}}
```
_START_TIME:_
```js
{{moment(slotDatePicker.selectedDate+" "+eventsTavble.selectedRow.slot.split(" - ")[0], "DD/MM/YYYY HH:mm").toISOString()}}
```
_END_TIME:_
```js
{{moment(slotDatePicker.selectedDate+" "+eventsTavble.selectedRow.slot.split(" - ")[1], "DD/MM/YYYY HH:mm").toISOString()}}
```
_DESCRIPTION:_
```js
{{purposeInput.text}}
```
_TITLE:_
```js
{{nameInput.text + " Meeting with Nikhil"}}
```
_EMAIL:_
```js
{{emailInput.text + " Meeting with Nikhil"}}
```
With this, our API is reading values from the `eventsTable` and the slotDatePicker to create the right event for the selected slot (`eventsTable.selectedRow`). We can now bind the onClick of the confirm button to call the `create_event` API and close the modal `onSuccess`.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hr2bwasrcgr9bb3n6xh.gif" width="100%" alt="creating-cal-success" />

## Setting up Zoom for Video Conferencing

Finally, there's one more thing. We need to send out a Zoom link for every event to integrate Zoom's APIs and create a zoom link for the calendar events. (This is one of the things I love about Calendly) To integrate with zoom, we have to fetch a JWT to authenticate our application requests with zoom. We'll follow [this guide](https://marketplace.zoom.us/docs/guides/auth/jwt). With a JWT in hand, it became as easy as importing another CURL and naming it create_zoom.

```curl
curl --location --request POST 'https://api.zoom.us/v2/users/5IrMmK-8Rv-dFkX1kbk22w/meetings' \
--header 'Content-Type: text/plain' \
--data-raw '{
  "topic": "<TOPIC>",
  "type": 2,
  "start_time": "<START_TIME>",
  "duration": <DURATION>,
"timezone": "Asia/Kolkata",
  "settings": {
    "registrants_email_notification": true
  }
}'
```

Replace the following variables as shown:

_JWT_ with your JWT token

_TOPIC:_
```js
{{nameInput.text + "<> Name"}}
```
_STARTTIME:_
```js
{moment(slotDatePicker.selectedDate+" "+eventsTable.selectedRow.slot.split(" - ")[0], "DD/MM/YYYY HH:mm").format("yyyy-MM-DDTHH:mm:ss")}} 
```
-DURATION:_ 
```js
{{durationDropdown.selectedOptionValue}}
```

We also have to update our create_event API to save the zoom link returned by this API in the calendar event. Update the description field to

```js
{{purposeInput.text + "\n Zoom Link: " + create_zoom.data.join_url }} 
```

Now let's call our `create_zoom` API before we call `create_event` and close the modal. To do this, we can easily convert the onClick configuration to javascript and write a neat little workflow using callbacks.

```js
{{ create_zoom.run(() => 
    create_event_event.run(() => 
      fetch_calendar_events.run(() => 
        closeModal("Modal1")
      )
    )
  )
}}
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q3659hvbi4x5d86pbu9n.gif)

Now hit deploy, make your application URL public by clicking the share button, and share it with anyone you need to meet. You can try my meeting scheduler here. Soon we’ll be having a fork app feature on Appsmith so that we can directly fork this one and customise accordingly within no time. All we’ll have to do is update the forked app with the new API keys. 

**If you like what we've built, star our ⭐️ [Github](https://github.com/appsmithorg/appsmith) Repo.

Also, do let us know your thoughts on this article in the comments section.