By Nikhil Nandagopal and Vihar Kurama
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!
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 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 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 or sign up for a new one (it's free!). We’ll walk through different steps to list our events!
- First, you’ll have to create a new application on Appsmith.
- A new application opens up an application titled Untitled Application 1; you can rename it by double-clicking on the existing one.
- 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.
- Add a new API and save it as a data source with the following URL:
https://www.googleapis.com/calendar
- You can also set the name of the data source; in this case, we’ll call it GCalender.
- Now, navigate to the GCalender data source and set the following configuration:
Authentication Type
:Oauth 2.0
Grant Type
:Authorization Code
- 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. - Add the Client ID and Client Secret from Google Cloud Platform
- 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
- 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 --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.
// 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.
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:
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
.
{{
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.
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 --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:
{{nameInput.text}}
START_TIME:
{{moment(slotDatePicker.selectedDate+" "+eventsTavble.selectedRow.slot.split(" - ")[0], "DD/MM/YYYY HH:mm").toISOString()}}
END_TIME:
{{moment(slotDatePicker.selectedDate+" "+eventsTavble.selectedRow.slot.split(" - ")[1], "DD/MM/YYYY HH:mm").toISOString()}}
DESCRIPTION:
{{purposeInput.text}}
TITLE:
{{nameInput.text + " Meeting with Nikhil"}}
EMAIL:
{{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
.
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. With a JWT in hand, it became as easy as importing another CURL and naming it create_zoom.
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:
{{nameInput.text + "<> Name"}}
STARTTIME:
{moment(slotDatePicker.selectedDate+" "+eventsTable.selectedRow.slot.split(" - ")[0], "DD/MM/YYYY HH:mm").format("yyyy-MM-DDTHH:mm:ss")}}
-DURATION:_
{{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
{{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.
{{ create_zoom.run(() =>
create_event_event.run(() =>
fetch_calendar_events.run(() =>
closeModal("Modal1")
)
)
)
}}
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 Repo.
Also, do let us know your thoughts on this article in the comments section.
Top comments (3)
Would it be too much trouble to make this into a video? I'm having difficulties with some of the parts as what I'm seeing in my screen does not match the prints in your guide.
Can you give any info about microsoft flow ?
where should I replace the JWT token ?