<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Airkit</title>
    <description>The latest articles on DEV Community by Airkit (@airkit).</description>
    <link>https://dev.to/airkit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F6073%2F27fb83c7-c89f-48cb-9077-3af0c5adb2f2.png</url>
      <title>DEV Community: Airkit</title>
      <link>https://dev.to/airkit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/airkit"/>
    <language>en</language>
    <item>
      <title>How to build a Calendly clone using Airkit's low code platform</title>
      <dc:creator>Ismaen Aboubakare 🥑</dc:creator>
      <pubDate>Mon, 17 Oct 2022 21:54:21 +0000</pubDate>
      <link>https://dev.to/airkit/how-to-build-a-calendly-clone-using-airkits-low-code-platform-1flm</link>
      <guid>https://dev.to/airkit/how-to-build-a-calendly-clone-using-airkits-low-code-platform-1flm</guid>
      <description>&lt;p&gt;I use  &lt;a href="https://calendly.com/"&gt;Calendly&lt;/a&gt;  a lot in my day to day to book meetings that fit within my schedule, and as I continued to use it, I wanted to see if I could build the core functionality of the tool in Airkit. There were a few things I wanted to make sure we could do in Airkit, which is connect to both google calendar and zoom, be able to programmatically get all of the free slots on my calendar in 30 minute increments, and programmatically generate zoom links attached to calendar invites.&lt;/p&gt;

&lt;p&gt;With that in mind, this tutorial will walk through the steps on how to build a Calendly clone, integrated with Google Calendar and Zoom. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ot9mhYDm4Jk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Overview
&lt;/h2&gt;

&lt;p&gt;When a user visits the application, they will see a calendar with an availability list of 30 minute appointment slots where they can choose and confirm an appointment time of their choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r-7us56A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://test-airkit.pantheonsite.io/app/uploads/2022/09/Calendly-Application-Overview-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r-7us56A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://test-airkit.pantheonsite.io/app/uploads/2022/09/Calendly-Application-Overview-1.png" alt="This image has an empty alt attribute; its file name is Calendly-Application-Overview-1.png" width="880" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the user selects the time slot. They will be presented with a form where they enter details about themselves (name and email address), add additional guests, and a message for the meeting. When they click on Schedule Event, Airkit will then create an appointment, send out meeting invitations to all participants along with a Zoom Meeting link.&lt;/p&gt;

&lt;p&gt;You can check the application out here:  &lt;a href="https://app.airkit.com/calendly"&gt;https://app.airkit.com/calendly&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To build along with this tutorial, you will need a few things to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Access to Airkit Studio&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://developers.google.com/"&gt;A Google Developer account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://developers.zoom.us/"&gt;A Zoom Developer account&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating APIs for the Google Calendar and Zoom Meetings
&lt;/h2&gt;

&lt;p&gt;Let’s start with the Google Calendar API. One of the first things you will need to do to get started is create a project in the Google Developer Console. You will need to create a new project called Calendly Airkit and then select it. Next add the Google Calendar API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D4hrToNH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/MvuuRfsXFwe6eyrx1LSCS-5_aPrx9JMCfVewvs5A5R0gZFLRzzFvHgrOocYr1xye_JOqMbQMACZXfALK57vVqSvJJtzENlIeNJuRhhL9s5iQ5F14O95OQiNKAZQkUGwDqqz7hc6nHMGygnkmo45wzOJuLthEtZ7-aXp2LBJh20E62aOLuPi1VQMlYQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D4hrToNH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/MvuuRfsXFwe6eyrx1LSCS-5_aPrx9JMCfVewvs5A5R0gZFLRzzFvHgrOocYr1xye_JOqMbQMACZXfALK57vVqSvJJtzENlIeNJuRhhL9s5iQ5F14O95OQiNKAZQkUGwDqqz7hc6nHMGygnkmo45wzOJuLthEtZ7-aXp2LBJh20E62aOLuPi1VQMlYQ" alt="This image has an empty alt attribute; its file name is MvuuRfsXFwe6eyrx1LSCS-5_aPrx9JMCfVewvs5A5R0gZFLRzzFvHgrOocYr1xye_JOqMbQMACZXfALK57vVqSvJJtzENlIeNJuRhhL9s5iQ5F14O95OQiNKAZQkUGwDqqz7hc6nHMGygnkmo45wzOJuLthEtZ7-aXp2LBJh20E62aOLuPi1VQMlYQ" width="880" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After enabling the Google Calendar API, you will need to configure the Credentials with an OAuth client id.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J4Xv5_yH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/W4jKyNXZM5NY52GJ-MqpohrPPpjYf8kPtHWuU2cwF2DanW-EO-V8GxLFjreTvZa9PYMo2yX9wkESQRWlRcG7krs2bradoZaanw5yaPnHIu5uou2CmTRb36Rzs8a6ztg3xRJhF4Zohnf8UVmEPwJHUzLwSuGF7axT7QW3UOVVdJglLkpa3GXnV43E-Q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J4Xv5_yH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/W4jKyNXZM5NY52GJ-MqpohrPPpjYf8kPtHWuU2cwF2DanW-EO-V8GxLFjreTvZa9PYMo2yX9wkESQRWlRcG7krs2bradoZaanw5yaPnHIu5uou2CmTRb36Rzs8a6ztg3xRJhF4Zohnf8UVmEPwJHUzLwSuGF7axT7QW3UOVVdJglLkpa3GXnV43E-Q" alt="This image has an empty alt attribute; its file name is W4jKyNXZM5NY52GJ-MqpohrPPpjYf8kPtHWuU2cwF2DanW-EO-V8GxLFjreTvZa9PYMo2yX9wkESQRWlRcG7krs2bradoZaanw5yaPnHIu5uou2CmTRb36Rzs8a6ztg3xRJhF4Zohnf8UVmEPwJHUzLwSuGF7axT7QW3UOVVdJglLkpa3GXnV43E-Q" width="880" height="866"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide the OAuth consent screen with the appropriate information and then you will need configure any scopes which are necessary for the Google Calendar API to work with this newly established Project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Google Calendar API – &lt;code&gt;/auth/calendar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Google Calendar API – &lt;code&gt;/auth/calendar.readonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Google Calendar API – &lt;code&gt;/auth/calendar.events&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bR5kZDyU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/3zOMPQiEKrc3Ichm1AAiCW9rqslGpshTv31kKKZApaRLloboTsfN04_dlKKiKHSWIjidDdHgFldjkdFA1PASO9V_YMfh-08OyVmPMAXxBd1TFO0XnVzoHxOIiQRUkaasrdHl2ky7YB5o2YsLstutQo9gsB6l6LJ4zYgfcn9jHNj6E8pEYLKhC0YWPQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bR5kZDyU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/3zOMPQiEKrc3Ichm1AAiCW9rqslGpshTv31kKKZApaRLloboTsfN04_dlKKiKHSWIjidDdHgFldjkdFA1PASO9V_YMfh-08OyVmPMAXxBd1TFO0XnVzoHxOIiQRUkaasrdHl2ky7YB5o2YsLstutQo9gsB6l6LJ4zYgfcn9jHNj6E8pEYLKhC0YWPQ" alt="This image has an empty alt attribute; its file name is 3zOMPQiEKrc3Ichm1AAiCW9rqslGpshTv31kKKZApaRLloboTsfN04_dlKKiKHSWIjidDdHgFldjkdFA1PASO9V_YMfh-08OyVmPMAXxBd1TFO0XnVzoHxOIiQRUkaasrdHl2ky7YB5o2YsLstutQo9gsB6l6LJ4zYgfcn9jHNj6E8pEYLKhC0YWPQ" width="743" height="792"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now are you able to create the OAuth Credential. You will need the redirect URI which can be found in this  &lt;a href="https://support.airkit.com/reference/custom-integrations"&gt;doc&lt;/a&gt;. Because my organization is in the US region, I use the below redirect URI.&lt;/p&gt;

&lt;p&gt;“`&lt;/p&gt;

&lt;p&gt;&lt;a href="https://us.api.prod.airkit.com/internal/sessions/v1/auth/callback"&gt;https://us.api.prod.airkit.com/internal/sessions/v1/auth/callback&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“`&lt;/p&gt;

&lt;p&gt;Next let’s configure the Zoom Developer API. Like Google, you will need a Zoom Developer account. You will be creating an OAuth app type. The app will be an account level application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1W1ExzNH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/DrJpawWYxghsFxHTzriIfxsCsvBNILv64Oj0_FS3eDhTbpHIFMpuKM_uy3kfSyx5aYsmaDz1gtmnYv5vVa0ZxULT_xroI0FaOJxaeiMMCbDuv3XYa73Jztdr0IPpEVxMxe4Ys19yyIUcclpguhvmolnXZ7ZJxx9p6XlBwsni4wzgXyvsd0B4Oz29IA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1W1ExzNH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/DrJpawWYxghsFxHTzriIfxsCsvBNILv64Oj0_FS3eDhTbpHIFMpuKM_uy3kfSyx5aYsmaDz1gtmnYv5vVa0ZxULT_xroI0FaOJxaeiMMCbDuv3XYa73Jztdr0IPpEVxMxe4Ys19yyIUcclpguhvmolnXZ7ZJxx9p6XlBwsni4wzgXyvsd0B4Oz29IA" alt="This image has an empty alt attribute; its file name is DrJpawWYxghsFxHTzriIfxsCsvBNILv64Oj0_FS3eDhTbpHIFMpuKM_uy3kfSyx5aYsmaDz1gtmnYv5vVa0ZxULT_xroI0FaOJxaeiMMCbDuv3XYa73Jztdr0IPpEVxMxe4Ys19yyIUcclpguhvmolnXZ7ZJxx9p6XlBwsni4wzgXyvsd0B4Oz29IA" width="880" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will be using the information from the App credentials later. Like before refer to the Custom Integrations documentation for the URL information for Redirect URL and Allow List. Now you are able to create the application. Finally you will need to add Scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  View and manage all user meetings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2itOF9G_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/D22kKxY2B6qWVaIgrkNUtyxDkxqQ9WQU99cSb4s-d1vnWWpQRGYEjz1uqfQDr5SOCP6KnTdZJfoInb-d4jQF6HHOQUg_6FBLL0_hQpF6qITlep5XZs0aVUM5LBygQMbgIzluO-52JrhuqAD3sPgSSN22Vn2zInENq7ABRvrjkYiH8_6N6b6GMPYxmg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2itOF9G_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/D22kKxY2B6qWVaIgrkNUtyxDkxqQ9WQU99cSb4s-d1vnWWpQRGYEjz1uqfQDr5SOCP6KnTdZJfoInb-d4jQF6HHOQUg_6FBLL0_hQpF6qITlep5XZs0aVUM5LBygQMbgIzluO-52JrhuqAD3sPgSSN22Vn2zInENq7ABRvrjkYiH8_6N6b6GMPYxmg" alt="This image has an empty alt attribute; its file name is D22kKxY2B6qWVaIgrkNUtyxDkxqQ9WQU99cSb4s-d1vnWWpQRGYEjz1uqfQDr5SOCP6KnTdZJfoInb-d4jQF6HHOQUg_6FBLL0_hQpF6qITlep5XZs0aVUM5LBygQMbgIzluO-52JrhuqAD3sPgSSN22Vn2zInENq7ABRvrjkYiH8_6N6b6GMPYxmg" width="880" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All of our integrations setup on Google Cloud and Zoom are done and ready to to be integrated into Airkit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Integration
&lt;/h2&gt;

&lt;p&gt;Now that you have the API integrations setup with Zoom and Google Calendar, it’s time to create the custom integration within our Airkit application. In the Airkit Console, go to the Integration tab. We will be creating two custom integrations. First, we will create a custom integration with Zoom and then will create a connected account to our application there. Next, we will repeat this process again with Google Calendar .&lt;/p&gt;

&lt;h3&gt;
  
  
  Zoom Integration
&lt;/h3&gt;

&lt;p&gt;Create a new Custom Integration, with a name and key that you indicate an integration with Zoom. The authentication type we are going to use is OAuth 2.0. The Integration Parameters are &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vgSJLOP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/InilW5GmtBDphFyBbjogAa2_KHhUaQuR-Ft5OfZ94JL3LY9yP5wPMVTKlfWYat4PPGN83bQY4WJwl-YaklLYRsmYZMg_VtJLecIfznGiIcm7lvI0J8T3ZoeuJ2iaHf8XmJxFP6b4eP9DZCxWPLSMvcGe6P4AKRIahWuT7T83qKZp7-FlV9rAIATrKQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vgSJLOP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/InilW5GmtBDphFyBbjogAa2_KHhUaQuR-Ft5OfZ94JL3LY9yP5wPMVTKlfWYat4PPGN83bQY4WJwl-YaklLYRsmYZMg_VtJLecIfznGiIcm7lvI0J8T3ZoeuJ2iaHf8XmJxFP6b4eP9DZCxWPLSMvcGe6P4AKRIahWuT7T83qKZp7-FlV9rAIATrKQ" alt="This image has an empty alt attribute; its file name is InilW5GmtBDphFyBbjogAa2_KHhUaQuR-Ft5OfZ94JL3LY9yP5wPMVTKlfWYat4PPGN83bQY4WJwl-YaklLYRsmYZMg_VtJLecIfznGiIcm7lvI0J8T3ZoeuJ2iaHf8XmJxFP6b4eP9DZCxWPLSMvcGe6P4AKRIahWuT7T83qKZp7-FlV9rAIATrKQ" width="396" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OAuth2 Configuration will be using the following configuration parameters listed below and shown in the sample:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Authorization Grant Type: Authorization Code&lt;/li&gt;
&lt;li&gt;  Access Endpoint: “&lt;a href="https://zoom.us/oauth/token%E2%80%9D"&gt;https://zoom.us/oauth/token”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Access Token Verb: POST&lt;/li&gt;
&lt;li&gt;  Authorization Endpoint: “&lt;a href="https://zoom.us/oauth/authorize%E2%80%9D"&gt;https://zoom.us/oauth/authorize”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  OAuth Scope: meeting:write:admin&lt;/li&gt;
&lt;li&gt;  Client ID: &lt;code&gt;{client_id}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Client Secret: &lt;code&gt;{client_secret}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gssOVsTR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/sMBqPdFGDmyYUgxoeEzY6vZqkRu6X2u6LwqqA5LL4dGBsaTkV5QI9-rYiIz0cZuQw-HkpzGiZOQ8rTQXwP61syllBQSs8ca_E466hv9-hbVy0VJI0eYYl8oR8oPp2aNm4PVYcPbPyjwie4WfcZlVBVpDrpaj1aX_8X6rPUgfKgUgHk-NklhOf1CZ4Q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gssOVsTR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/sMBqPdFGDmyYUgxoeEzY6vZqkRu6X2u6LwqqA5LL4dGBsaTkV5QI9-rYiIz0cZuQw-HkpzGiZOQ8rTQXwP61syllBQSs8ca_E466hv9-hbVy0VJI0eYYl8oR8oPp2aNm4PVYcPbPyjwie4WfcZlVBVpDrpaj1aX_8X6rPUgfKgUgHk-NklhOf1CZ4Q" alt="This image has an empty alt attribute; its file name is sMBqPdFGDmyYUgxoeEzY6vZqkRu6X2u6LwqqA5LL4dGBsaTkV5QI9-rYiIz0cZuQw-HkpzGiZOQ8rTQXwP61syllBQSs8ca_E466hv9-hbVy0VJI0eYYl8oR8oPp2aNm4PVYcPbPyjwie4WfcZlVBVpDrpaj1aX_8X6rPUgfKgUgHk-NklhOf1CZ4Q" width="396" height="838"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Auth token configuration will be the following configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Token Parameter: Header&lt;/li&gt;
&lt;li&gt;  Token Parameter Name: Authorization&lt;/li&gt;
&lt;li&gt;  Token Parameter Value Template: Bearer {token}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lT0FqNCQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/ZUYoYaT1EE6r9N3vxn8M-mEjAVo_aFvvXyyHAxBh0LsBhIAfuyKd5f20hSnFgpzJGd8XLh-d9Jk9r0jLx5qffRa_wcdjAkfzW8bYf4TwhyE398g3WGjilRMCXTFce_vX4mB-LryTSO9Ym4V98NBO7I-qPPPBX8E0wYWJEWstbTimZgPZkIVpAUTJGQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lT0FqNCQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/ZUYoYaT1EE6r9N3vxn8M-mEjAVo_aFvvXyyHAxBh0LsBhIAfuyKd5f20hSnFgpzJGd8XLh-d9Jk9r0jLx5qffRa_wcdjAkfzW8bYf4TwhyE398g3WGjilRMCXTFce_vX4mB-LryTSO9Ym4V98NBO7I-qPPPBX8E0wYWJEWstbTimZgPZkIVpAUTJGQ" alt="This image has an empty alt attribute; its file name is ZUYoYaT1EE6r9N3vxn8M-mEjAVo_aFvvXyyHAxBh0LsBhIAfuyKd5f20hSnFgpzJGd8XLh-d9Jk9r0jLx5qffRa_wcdjAkfzW8bYf4TwhyE398g3WGjilRMCXTFce_vX4mB-LryTSO9Ym4V98NBO7I-qPPPBX8E0wYWJEWstbTimZgPZkIVpAUTJGQ" width="396" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Calendar Integration
&lt;/h3&gt;

&lt;p&gt;Create a new Custom Integration, with a name and key that you indicate an integration with Google Calendar. We will follow similar steps as done for the Zoom Integration. The authentication type we are going to use is OAuth 2.0. The Integration Parameters are &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c_ZX6lyx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/FYUNjcXQqua-C2NRcuCYxm3SDC9JJ7cOWd4BL6EkLiYLHtf1Hxevix1malnLK5MM8qvm6VDCdOe45g1LqjocDo3lwUxcQXDpp6zR_VAd797jKhAIkv_JxI0On_6yNn6lC3nCPMfxsD3EPpVOS18A9PeQwJKxUAciOk8qIKWpRHEKBbb_jr4toy8c5g" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c_ZX6lyx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/FYUNjcXQqua-C2NRcuCYxm3SDC9JJ7cOWd4BL6EkLiYLHtf1Hxevix1malnLK5MM8qvm6VDCdOe45g1LqjocDo3lwUxcQXDpp6zR_VAd797jKhAIkv_JxI0On_6yNn6lC3nCPMfxsD3EPpVOS18A9PeQwJKxUAciOk8qIKWpRHEKBbb_jr4toy8c5g" alt="This image has an empty alt attribute; its file name is FYUNjcXQqua-C2NRcuCYxm3SDC9JJ7cOWd4BL6EkLiYLHtf1Hxevix1malnLK5MM8qvm6VDCdOe45g1LqjocDo3lwUxcQXDpp6zR_VAd797jKhAIkv_JxI0On_6yNn6lC3nCPMfxsD3EPpVOS18A9PeQwJKxUAciOk8qIKWpRHEKBbb_jr4toy8c5g" width="396" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OAuth2 Configuration will be using the following configuration parameters listed below and shown in the sample:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Authorization Grant Type: Authorization Code&lt;/li&gt;
&lt;li&gt;  Access Endpoint: “&lt;a href="https://oauth2.googleapis.com/token%E2%80%9D"&gt;https://oauth2.googleapis.com/token”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Access Token Verb: POST&lt;/li&gt;
&lt;li&gt;  Authorization Endpoint: “&lt;a href="https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&amp;amp;prompt=consent%E2%80%9D"&gt;https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&amp;amp;prompt=consent”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  OAuth Scope:

&lt;ul&gt;
&lt;li&gt;  “&lt;a href="https://www.googleapis.com/auth/calendar%E2%80%9D"&gt;https://www.googleapis.com/auth/calendar”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  “&lt;a href="https://www.googleapis.com/auth/calendar.readonly%E2%80%9D"&gt;https://www.googleapis.com/auth/calendar.readonly”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  “&lt;a href="https://www.googleapis.com/auth/calendar.events%E2%80%9D"&gt;https://www.googleapis.com/auth/calendar.events”&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;  Client ID: &lt;code&gt;{client_id}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  Client Secret: &lt;code&gt;{client_secret}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hA6KJtIj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/9_zmMt2G8vx36tgCacb3wP8EN9CGcE-iuXbL92tJMx_KmiWSU3Hv7j3nTyayNYL-5bVSZBqWHBVs7FiCCO3LEiVBzSHaLhGPZAwgqdHoWaRmaNxDcVd7C2LZQdi91Ke3lePDTUT_N4t24bMfx0idF6lu5WckEPAWlfT-EVjdCGfBLnXdZ_9BWFqZMQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hA6KJtIj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/9_zmMt2G8vx36tgCacb3wP8EN9CGcE-iuXbL92tJMx_KmiWSU3Hv7j3nTyayNYL-5bVSZBqWHBVs7FiCCO3LEiVBzSHaLhGPZAwgqdHoWaRmaNxDcVd7C2LZQdi91Ke3lePDTUT_N4t24bMfx0idF6lu5WckEPAWlfT-EVjdCGfBLnXdZ_9BWFqZMQ" alt="This image has an empty alt attribute; its file name is 9_zmMt2G8vx36tgCacb3wP8EN9CGcE-iuXbL92tJMx_KmiWSU3Hv7j3nTyayNYL-5bVSZBqWHBVs7FiCCO3LEiVBzSHaLhGPZAwgqdHoWaRmaNxDcVd7C2LZQdi91Ke3lePDTUT_N4t24bMfx0idF6lu5WckEPAWlfT-EVjdCGfBLnXdZ_9BWFqZMQ" width="396" height="839"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Auth token configuration will be the following configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Token Parameter is Header&lt;/li&gt;
&lt;li&gt;  Token Parameter Name is Authorization&lt;/li&gt;
&lt;li&gt;  Token Parameter Value Template is Bearer {token}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WCH0d4mo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/4Lt7iptIQjU-Tls8e82NDiwLyHfCn_Hy6g8s7b8yL71HdQ_vSIOs0TZqdl_lfXeUg_u-bT6oCXwFvSZV9MK-RJyMkliS9wwQZfbRB6T0w0xBGrdnalb16KEILV0mKhGtikUWvcTzNWXIj9siVLRiEX7OvCHvYPTYpF4y5ptOwtaPxvvKEeUbPSVE5A" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WCH0d4mo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/4Lt7iptIQjU-Tls8e82NDiwLyHfCn_Hy6g8s7b8yL71HdQ_vSIOs0TZqdl_lfXeUg_u-bT6oCXwFvSZV9MK-RJyMkliS9wwQZfbRB6T0w0xBGrdnalb16KEILV0mKhGtikUWvcTzNWXIj9siVLRiEX7OvCHvYPTYpF4y5ptOwtaPxvvKEeUbPSVE5A" alt="This image has an empty alt attribute; its file name is 4Lt7iptIQjU-Tls8e82NDiwLyHfCn_Hy6g8s7b8yL71HdQ_vSIOs0TZqdl_lfXeUg_u-bT6oCXwFvSZV9MK-RJyMkliS9wwQZfbRB6T0w0xBGrdnalb16KEILV0mKhGtikUWvcTzNWXIj9siVLRiEX7OvCHvYPTYpF4y5ptOwtaPxvvKEeUbPSVE5A" width="396" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Refer to  &lt;a href="https://www.youtube.com/watch?v=ot9mhYDm4Jk&amp;amp;t=1710s"&gt;this snippet of the video&lt;/a&gt;  on how to pair the connected accounts for Zoom and Google Calendar API or refer to  &lt;a href="https://support.airkit.com/reference/custom-integrations#integration-parameters"&gt;this document on Connected accounts&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functionality through Data Flows
&lt;/h2&gt;

&lt;p&gt;Now we have the integrations ready for use in the Airkit Application, next we will need to build out six Data Flows for the application for self-service appointment scheduling. Refer to the YouTube Video for complete details on how to create all these Data Flows.&lt;/p&gt;

&lt;p&gt;First, we will create three Data Flows for finding all the available time slots on a particular day. The main Data Flow will be called  &lt;strong&gt;“Get Free Slots”&lt;/strong&gt; and then we will create two additional supporting Data Flows called  &lt;strong&gt;“Merge Objects”&lt;/strong&gt;  and  &lt;strong&gt;“Generate 30 Min Slots”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QNC1hEC6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/XM3bqx3IbEY4UQrrCPe0d6yO0xV7Pum98icKlIKCKM3NMV2GGoVdSNLz2txcl2wabi75AizGu3f8K8jwb2HnWyPZhFMTbhx85GehNLoOfJL86ECVYlughYBi1pzLBcSVpEPX-Yd_TUugUKi7qpteN2N0sMay-D7FRCpGIvbSuQSslSmOKF0lrFFRIg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QNC1hEC6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/XM3bqx3IbEY4UQrrCPe0d6yO0xV7Pum98icKlIKCKM3NMV2GGoVdSNLz2txcl2wabi75AizGu3f8K8jwb2HnWyPZhFMTbhx85GehNLoOfJL86ECVYlughYBi1pzLBcSVpEPX-Yd_TUugUKi7qpteN2N0sMay-D7FRCpGIvbSuQSslSmOKF0lrFFRIg" alt="This image has an empty alt attribute; its file name is XM3bqx3IbEY4UQrrCPe0d6yO0xV7Pum98icKlIKCKM3NMV2GGoVdSNLz2txcl2wabi75AizGu3f8K8jwb2HnWyPZhFMTbhx85GehNLoOfJL86ECVYlughYBi1pzLBcSVpEPX-Yd_TUugUKi7qpteN2N0sMay-D7FRCpGIvbSuQSslSmOKF0lrFFRIg" width="172" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get Free Slots Data Flow:&lt;/strong&gt; We will be setting the appointment window starting time and ending time, finding the existing appointments via the Google Calendar API, and creating a list of 30 minute available time slots.&lt;/p&gt;

&lt;p&gt;Google Calendar API expects the date to be in RFC 3339 format. We will need to use the following Airscript function: &lt;code&gt;FORMAT_DATETIME&lt;/code&gt; in order to format the date into RFC 3339.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HeQv5JGK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/WMN7r3x_ACrSTfKP_wgFPrpMt5L7X3Rqygd48mH7m-Wff7LR0ofo4_FYCURIe7dbpT1sVuzEURCVdsmbxYtqirY0YXBAOcyXck30CVfU7WiKL2XBZbej0lxBbb6I5BrY3TADPT2aVwUgIb-roat_zF1oR59MT1T319A3kspZ36MKoAK5iYMrv7xoZg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HeQv5JGK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/WMN7r3x_ACrSTfKP_wgFPrpMt5L7X3Rqygd48mH7m-Wff7LR0ofo4_FYCURIe7dbpT1sVuzEURCVdsmbxYtqirY0YXBAOcyXck30CVfU7WiKL2XBZbej0lxBbb6I5BrY3TADPT2aVwUgIb-roat_zF1oR59MT1T319A3kspZ36MKoAK5iYMrv7xoZg" alt="This image has an empty alt attribute; its file name is WMN7r3x_ACrSTfKP_wgFPrpMt5L7X3Rqygd48mH7m-Wff7LR0ofo4_FYCURIe7dbpT1sVuzEURCVdsmbxYtqirY0YXBAOcyXck30CVfU7WiKL2XBZbej0lxBbb6I5BrY3TADPT2aVwUgIb-roat_zF1oR59MT1T319A3kspZ36MKoAK5iYMrv7xoZg" width="789" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can use this &lt;code&gt;FORMAT_DATE&lt;/code&gt; Airscript in a Transform Function for both the Start of the Day and End of the Day Variable.&lt;/p&gt;

&lt;p&gt;&amp;lt;&lt;a href="https://support.airkit.com/reference/format_datetime"&gt;https://support.airkit.com/reference/format_datetime&lt;/a&gt;&amp;gt;&lt;/p&gt;

&lt;p&gt;Now we will need information from Google Calendar API to find the busy times so we will leverage the integration we set up already. In order to make this request, we will use HTTP Request Data operation and use the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Service: Google Calendar API&lt;/li&gt;
&lt;li&gt;  Method: POST&lt;/li&gt;
&lt;li&gt;  URL: “&lt;a href="https://www.googleapis.com/calendar/v3/freeBusy"&gt;https://www.googleapis.com/calendar/v3/freeBusy&lt;/a&gt;“&lt;/li&gt;
&lt;li&gt;  Content Type: “application/json”&lt;/li&gt;
&lt;li&gt;  Body will use results from previous Data Operations show in the screen shot below&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AYVGRJbC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/gd9SkwiM7Zk4e4jZ6Ae0Ee8P6BgT5QDay0_W4fIGkfuWt1SnYn9icP-GodPaKWSKaPO7DV_G6xEBOhb4jiaD_PB3JRxFOohIb9TNFaizssta1sVFx4VRtGw4nAFT3rNa1Enc-cVLDWd1ygByPBFBuX2gsSjnwJfwrlea8zqFtTExogt434INe9mIaQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AYVGRJbC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/gd9SkwiM7Zk4e4jZ6Ae0Ee8P6BgT5QDay0_W4fIGkfuWt1SnYn9icP-GodPaKWSKaPO7DV_G6xEBOhb4jiaD_PB3JRxFOohIb9TNFaizssta1sVFx4VRtGw4nAFT3rNa1Enc-cVLDWd1ygByPBFBuX2gsSjnwJfwrlea8zqFtTExogt434INe9mIaQ" alt="This image has an empty alt attribute; its file name is gd9SkwiM7Zk4e4jZ6Ae0Ee8P6BgT5QDay0_W4fIGkfuWt1SnYn9icP-GodPaKWSKaPO7DV_G6xEBOhb4jiaD_PB3JRxFOohIb9TNFaizssta1sVFx4VRtGw4nAFT3rNa1Enc-cVLDWd1ygByPBFBuX2gsSjnwJfwrlea8zqFtTExogt434INe9mIaQ" width="376" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The resulting busy time slots will look like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_19WMalv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/X-Cl7pDhqPONvC1rYEXVW0HzFiT8VnM6ABjuKaFlsGOtaHcNyQlNYfCwRwD9CrOi5D4uTGW7IeSKp5kvcU-aDWyPv_dff2GUF8gNZmPlPEUuy3NEQ7KwzT4hbHrttRZZ5rmIrXk4rbICYfroHaI1xuPlnL1DNSjgNENE5wQZocLXMmYat8IkHkS2IA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_19WMalv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/X-Cl7pDhqPONvC1rYEXVW0HzFiT8VnM6ABjuKaFlsGOtaHcNyQlNYfCwRwD9CrOi5D4uTGW7IeSKp5kvcU-aDWyPv_dff2GUF8gNZmPlPEUuy3NEQ7KwzT4hbHrttRZZ5rmIrXk4rbICYfroHaI1xuPlnL1DNSjgNENE5wQZocLXMmYat8IkHkS2IA" alt="This image has an empty alt attribute; its file name is X-Cl7pDhqPONvC1rYEXVW0HzFiT8VnM6ABjuKaFlsGOtaHcNyQlNYfCwRwD9CrOi5D4uTGW7IeSKp5kvcU-aDWyPv_dff2GUF8gNZmPlPEUuy3NEQ7KwzT4hbHrttRZZ5rmIrXk4rbICYfroHaI1xuPlnL1DNSjgNENE5wQZocLXMmYat8IkHkS2IA" width="369" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge Objects Data Flow:&lt;/strong&gt; Since we have a few time slot objects, we will need to bring some different object data together and we will create and use another Data Flow called Merge Objects. This Data Flow will use the Airscript Function &lt;code&gt;[MERGE_OBJECT](https://support.airkit.com/reference/merge_objects)&lt;/code&gt;. Refer to the YouTube Video for more details on this Data Flow.&lt;/p&gt;

&lt;p&gt;Additionally there will be some additional time formatting needed to produce a list of 30 minute available time slots. Key Airscript functions will be  &lt;a href="https://support.airkit.com/reference/time_from_format"&gt;&lt;code&gt;TIME_FROM_FORMAT&lt;/code&gt;&lt;/a&gt;,  &lt;a href="https://support.airkit.com/reference/update_timezone"&gt;&lt;code&gt;UPDATE_TIMEZONE&lt;/code&gt;&lt;/a&gt;, and  &lt;a href="https://support.airkit.com/reference/datetime_from_format"&gt;&lt;code&gt;DATETIME_FROM_FORMAT&lt;/code&gt;&lt;/a&gt;  refer to the documentation to learn more about these functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate 30 min Slots Data Flow:&lt;/strong&gt; To generate a list of available time slots, we will create another supporting Data Flow called “Generate 30 min slots”. This Data Flow will take two inputs of start_time and end_time and create an object with a list of slots in 30 minute increments. In order to accomplish the list, use a  &lt;a href="https://support.airkit.com/docs/filtering-data-using-query-expression"&gt;Query Expression&lt;/a&gt;  to create the list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---4KI9W4S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/s6EIGdrgA5O0Yq9Ga_J85OUNWNI0tjhrL03BQgnJQNgGnevHYum0DQ5C2L3b_O2_WRDR-iDHA8vWznPN6CfUFtK6eHgOeIcz_bE5dRi1K2ydH-Y8-8tvGPpU62YFub7_ty8Rv1sOD5WMM_xgjHdiBm93RIulQ4_weFitWr3qH-WRlioZeMDJni6kjg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---4KI9W4S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/s6EIGdrgA5O0Yq9Ga_J85OUNWNI0tjhrL03BQgnJQNgGnevHYum0DQ5C2L3b_O2_WRDR-iDHA8vWznPN6CfUFtK6eHgOeIcz_bE5dRi1K2ydH-Y8-8tvGPpU62YFub7_ty8Rv1sOD5WMM_xgjHdiBm93RIulQ4_weFitWr3qH-WRlioZeMDJni6kjg" alt="This image has an empty alt attribute; its file name is s6EIGdrgA5O0Yq9Ga_J85OUNWNI0tjhrL03BQgnJQNgGnevHYum0DQ5C2L3b_O2_WRDR-iDHA8vWznPN6CfUFtK6eHgOeIcz_bE5dRi1K2ydH-Y8-8tvGPpU62YFub7_ty8Rv1sOD5WMM_xgjHdiBm93RIulQ4_weFitWr3qH-WRlioZeMDJni6kjg" width="625" height="41"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The list of time slots will be used later when we build the Airkit application.&lt;/p&gt;

&lt;p&gt;Now we will create the last three Data Flows which will schedule the Calendar event and create a Zoom Meeting. We will create another main Data Flow called “&lt;strong&gt;Schedule Event&lt;/strong&gt;” and two supporting Data Flows: “&lt;strong&gt;Create Zoom Meeting&lt;/strong&gt;” and “&lt;strong&gt;Create Google Calendar Event&lt;/strong&gt;”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LvxfAu5X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/qbs9Usdpx-W-pfDkMXW6jQo9LQKPxTneAPxGBI4Im1AWeszuY7qkmmNcv42qRWK2BVq86g1_IcYd8rooNsrKyTeG6FlQgdexVHWASyspDcmephqU2Hz7r7AIiv5Sg3kz4fyDnJMBhZo-akZ2tTbyY_6NOCOAoc6tyqFrbcWmf_mXgkwg34XWr49naA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LvxfAu5X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/qbs9Usdpx-W-pfDkMXW6jQo9LQKPxTneAPxGBI4Im1AWeszuY7qkmmNcv42qRWK2BVq86g1_IcYd8rooNsrKyTeG6FlQgdexVHWASyspDcmephqU2Hz7r7AIiv5Sg3kz4fyDnJMBhZo-akZ2tTbyY_6NOCOAoc6tyqFrbcWmf_mXgkwg34XWr49naA" alt="This image has an empty alt attribute; its file name is qbs9Usdpx-W-pfDkMXW6jQo9LQKPxTneAPxGBI4Im1AWeszuY7qkmmNcv42qRWK2BVq86g1_IcYd8rooNsrKyTeG6FlQgdexVHWASyspDcmephqU2Hz7r7AIiv5Sg3kz4fyDnJMBhZo-akZ2tTbyY_6NOCOAoc6tyqFrbcWmf_mXgkwg34XWr49naA" width="206" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Zoom Meeting Data Flow:&lt;/strong&gt; Let’s build this Data Flow! This Data Flow takes an input of starting time and a list of emails and then makes a HTTP request to Zoom for the meeting information.&lt;/p&gt;

&lt;p&gt;We will use the Zoom API integration we configured before and will use the HTTP Request Data operation with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Service: Zoom OAuth API&lt;/li&gt;
&lt;li&gt;  Method: POST&lt;/li&gt;
&lt;li&gt;  URL: “&lt;a href="https://api.zoom.us/v2/users/me/meetings%E2%80%9D"&gt;https://api.zoom.us/v2/users/me/meetings”&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Content type: “application/json”&lt;/li&gt;
&lt;li&gt;  Body will need the following parameters modified:

&lt;ul&gt;
&lt;li&gt;  “&lt;code&gt;schedule_for&lt;/code&gt;” : &lt;/li&gt;
&lt;li&gt;  “&lt;code&gt;Meeting_invitees&lt;/code&gt;” : Use a query expression to all the emails to send invites to

&lt;ul&gt;
&lt;li&gt;  FROM email IN &lt;code&gt;list_of_emails&lt;/code&gt; SELECT &lt;code&gt;{ “email”: email }&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;  “&lt;code&gt;start_time&lt;/code&gt;” : Use the start time and format appropriately for Zoom

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;“{{FORMAT_DATETIME(start_time, “yyyy-MM-ddTHH:mm:ss”)}}”&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FWylO21V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/JLXFzwVHVbY1LySZr0FhSqlrE18smYx4SQSMQBp0jNx5DQksNCHZSoLh9q3tM4a7Bu_bJqIMHm1HPwYykk6Zn8VMKCbdrdmunqSAbdSJEatx510lbSgDwY72Uw03fgsmgyEqVHgaGVJNCu7XGaGkWEKCIKCt6iWzBXi5AZd9UYcZJ0GkmmCce55UZw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FWylO21V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/JLXFzwVHVbY1LySZr0FhSqlrE18smYx4SQSMQBp0jNx5DQksNCHZSoLh9q3tM4a7Bu_bJqIMHm1HPwYykk6Zn8VMKCbdrdmunqSAbdSJEatx510lbSgDwY72Uw03fgsmgyEqVHgaGVJNCu7XGaGkWEKCIKCt6iWzBXi5AZd9UYcZJ0GkmmCce55UZw" alt="This image has an empty alt attribute; its file name is JLXFzwVHVbY1LySZr0FhSqlrE18smYx4SQSMQBp0jNx5DQksNCHZSoLh9q3tM4a7Bu_bJqIMHm1HPwYykk6Zn8VMKCbdrdmunqSAbdSJEatx510lbSgDwY72Uw03fgsmgyEqVHgaGVJNCu7XGaGkWEKCIKCt6iWzBXi5AZd9UYcZJ0GkmmCce55UZw" width="755" height="797"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The result can be customized accordingly with a Transform operation and then sent back to the  &lt;strong&gt;“Schedule Event”&lt;/strong&gt;  Data Flow to be used in the “&lt;strong&gt;Create Google Calendar Event&lt;/strong&gt;” Data Flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create Google Calendar Event Data Flow:&lt;/strong&gt; Let’s build it! This Data Flow takes the input of the following variables: &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;start_time&lt;/code&gt;, &lt;code&gt;end_time&lt;/code&gt;, &lt;code&gt;list_of_emails&lt;/code&gt;, and a &lt;code&gt;description&lt;/code&gt;. And then makes a HTTP request to Google Calendar API to create a Google Calendar Meeting.&lt;/p&gt;

&lt;p&gt;We will use the Google Calendar API integration we configured before and will use the HTTP Request Data operation with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Service: Google Calendar API&lt;/li&gt;
&lt;li&gt;  Method: POST&lt;/li&gt;
&lt;li&gt;  URL: “&lt;a href="https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1%22"&gt;https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1″&lt;/a&gt;“&lt;/li&gt;
&lt;li&gt;  Content type: “application/json”&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Body:&lt;/p&gt;

&lt;p&gt;{ “summary”: summary,&lt;/p&gt;

&lt;p&gt;“start”: { “dateTime”: FORMAT_DATETIME(start_time) },&lt;/p&gt;

&lt;p&gt;“end”: { “dateTime”: FORMAT_DATETIME(end_time) },&lt;/p&gt;

&lt;p&gt;“attendees”: FROM a IN emails SELECT { “email”: a },&lt;/p&gt;

&lt;p&gt;“description”: description}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--965_FVmB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/wpYX9i2OvY8PNAL2Fg9z7i1VuHc5nghzU0xhKwXopfs0pLTlc6b-IfZBFSJvQRZHt2YkvRJfK_SZWLo2cxd81qNtPUgjHX299b5T3IgWOAYCQU15bZsyW44GEL8ufPmn3zTL4t_sf-zL53Kl8s7TLNLI5DAyKCbAhv0Nj6CfzI7-X0VBXNjgSz_q7Q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--965_FVmB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/wpYX9i2OvY8PNAL2Fg9z7i1VuHc5nghzU0xhKwXopfs0pLTlc6b-IfZBFSJvQRZHt2YkvRJfK_SZWLo2cxd81qNtPUgjHX299b5T3IgWOAYCQU15bZsyW44GEL8ufPmn3zTL4t_sf-zL53Kl8s7TLNLI5DAyKCbAhv0Nj6CfzI7-X0VBXNjgSz_q7Q" alt="This image has an empty alt attribute; its file name is wpYX9i2OvY8PNAL2Fg9z7i1VuHc5nghzU0xhKwXopfs0pLTlc6b-IfZBFSJvQRZHt2YkvRJfK_SZWLo2cxd81qNtPUgjHX299b5T3IgWOAYCQU15bZsyW44GEL8ufPmn3zTL4t_sf-zL53Kl8s7TLNLI5DAyKCbAhv0Nj6CfzI7-X0VBXNjgSz_q7Q" width="755" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schedule Event Data Flow:&lt;/strong&gt; Let’s build it now! This is the main Data Flow which brings all the necessary data together to create the Google Calendar Event. The Input data variables are the following: selected_time, name, email, list_guest_email, and preparation notes which all will come from a web page form built later in the Building Airkit Application section. See the screenshot below to see how the Data Flows are configured or continue to watch the Youtube Video to see how this app is built.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3kxTIL7k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/MqIMYHw6YjACVFUNbf8nRUXVqF_r8iieVlZkfBoz26TVpZsCHbKWXMFbqN7zfaePkSkcnGmhzv4v0tZp1eLCxV-1DkpHXRa4IPVQNbJVuJuWXot5Ukgkc-4p5CiVivdBJ42_NmBy-8pcljiSmxtEHnQKAvAg354teidAsEErhwMFyzJoExoEAqdREw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3kxTIL7k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/MqIMYHw6YjACVFUNbf8nRUXVqF_r8iieVlZkfBoz26TVpZsCHbKWXMFbqN7zfaePkSkcnGmhzv4v0tZp1eLCxV-1DkpHXRa4IPVQNbJVuJuWXot5Ukgkc-4p5CiVivdBJ42_NmBy-8pcljiSmxtEHnQKAvAg354teidAsEErhwMFyzJoExoEAqdREw" alt="This image has an empty alt attribute; its file name is MqIMYHw6YjACVFUNbf8nRUXVqF_r8iieVlZkfBoz26TVpZsCHbKWXMFbqN7zfaePkSkcnGmhzv4v0tZp1eLCxV-1DkpHXRa4IPVQNbJVuJuWXot5Ukgkc-4p5CiVivdBJ42_NmBy-8pcljiSmxtEHnQKAvAg354teidAsEErhwMFyzJoExoEAqdREw" width="755" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wZN5hMyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/SatVBT50r4QkrnlwaPXVbmu4J-y5DWaxg9Gm48Hs60LL2G_jlq9YNZhGyagl2w4w2HhR67TcATg_BWG55-nnbuAoNTYwbX0Sd9ftDNuUhvr_X9-zRZZ6dE-qWk81Iw-jBd2XSzG-zRaxRUyMRPpaIs6z9Oh1pm0XAtw9_WJ7GHCstAWSjRSg1-bObQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wZN5hMyX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/SatVBT50r4QkrnlwaPXVbmu4J-y5DWaxg9Gm48Hs60LL2G_jlq9YNZhGyagl2w4w2HhR67TcATg_BWG55-nnbuAoNTYwbX0Sd9ftDNuUhvr_X9-zRZZ6dE-qWk81Iw-jBd2XSzG-zRaxRUyMRPpaIs6z9Oh1pm0XAtw9_WJ7GHCstAWSjRSg1-bObQ" alt="This image has an empty alt attribute; its file name is SatVBT50r4QkrnlwaPXVbmu4J-y5DWaxg9Gm48Hs60LL2G_jlq9YNZhGyagl2w4w2HhR67TcATg_BWG55-nnbuAoNTYwbX0Sd9ftDNuUhvr_X9-zRZZ6dE-qWk81Iw-jBd2XSzG-zRaxRUyMRPpaIs6z9Oh1pm0XAtw9_WJ7GHCstAWSjRSg1-bObQ" width="755" height="952"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Airkit Application
&lt;/h2&gt;

&lt;p&gt;Now we have the integrations and Data Flows ready for use in Airkit Application, next we will need to build out the application for self-service appointment scheduling.&lt;br&gt;
In Journey Builder, add a Visit a Link Trigger to start the application, and add a webflow in the first Journey step. Then in Web Builder, we will make three Web Pages. On the first page, a self-service appointment scheduler, we will add the following key controls: Label, Container, Date Picker and Container List with two Buttons. Make sure you label all the controls appropriately.&lt;/p&gt;

&lt;p&gt;The Web Page Tree should look like the following example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QlJ6hAqs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/GxfMkubV1mZA_gZIqc7iIX77EavUIepp071Jy_GFn52sP_YxbKgMCY41zq6lSDuE8fRR8YA3ZCoBdhhkq5j5WotjN7_A2BwG5FKGZwMSzGWfVEzH79ImK0Ob6WpmghmZpmZIv2eplGoEU7wpQKwvaYiwAexU3s0Nu-UE2OzYVhfS01RDTHU7RIzZ7Q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QlJ6hAqs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/GxfMkubV1mZA_gZIqc7iIX77EavUIepp071Jy_GFn52sP_YxbKgMCY41zq6lSDuE8fRR8YA3ZCoBdhhkq5j5WotjN7_A2BwG5FKGZwMSzGWfVEzH79ImK0Ob6WpmghmZpmZIv2eplGoEU7wpQKwvaYiwAexU3s0Nu-UE2OzYVhfS01RDTHU7RIzZ7Q" alt="This image has an empty alt attribute; its file name is GxfMkubV1mZA_gZIqc7iIX77EavUIepp071Jy_GFn52sP_YxbKgMCY41zq6lSDuE8fRR8YA3ZCoBdhhkq5j5WotjN7_A2BwG5FKGZwMSzGWfVEzH79ImK0Ob6WpmghmZpmZIv2eplGoEU7wpQKwvaYiwAexU3s0Nu-UE2OzYVhfS01RDTHU7RIzZ7Q" width="469" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you add these controls you can configure labels appropriately similar to what is shown in the Youtube Video or the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0eSZ9pn_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/uOyG4rpdFpkQp5CxLg82fbamTW9WdknjD2VnzFCKepskXzrhzxria7Wiwh0Z9XX7kXWEBJQh3lnzaWPu-YqJ_bOdwv1FoFhD4AHLM5TY8G-goyG0GH0JS5mqzjtc8DEdWMIVxnMPruYz4X6Cm32_wiJ-Ef8htrVdDXB2lZrt1xKNvz3oqS_z5cXteA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0eSZ9pn_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/uOyG4rpdFpkQp5CxLg82fbamTW9WdknjD2VnzFCKepskXzrhzxria7Wiwh0Z9XX7kXWEBJQh3lnzaWPu-YqJ_bOdwv1FoFhD4AHLM5TY8G-goyG0GH0JS5mqzjtc8DEdWMIVxnMPruYz4X6Cm32_wiJ-Ef8htrVdDXB2lZrt1xKNvz3oqS_z5cXteA" alt="This image has an empty alt attribute; its file name is uOyG4rpdFpkQp5CxLg82fbamTW9WdknjD2VnzFCKepskXzrhzxria7Wiwh0Z9XX7kXWEBJQh3lnzaWPu-YqJ_bOdwv1FoFhD4AHLM5TY8G-goyG0GH0JS5mqzjtc8DEdWMIVxnMPruYz4X6Cm32_wiJ-Ef8htrVdDXB2lZrt1xKNvz3oqS_z5cXteA" width="448" height="881"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we will configure the controls for the Date and Time Slot Selection. First we will configure the Date Picker control. You will configure the Control Properties as shown below to get a one month layout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QgDvMcpe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/vlFqoLIIafApCu6VJ5Zj3MSMTlUFCQh92VLBVqgdIRdBjJjDjS7967D_J1GOkTtOeanMUFs09_ESeeqJsf1eKJh9oafL4wzKWJr6HZvQgjrHRMsMae8BOw1m7V5HbO-31I8Iwnc3ItJpl3Y_D63PR9lQZSAg5bVLVUiIQkyt2j8Iwd9n6mqd2x3bnw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QgDvMcpe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/vlFqoLIIafApCu6VJ5Zj3MSMTlUFCQh92VLBVqgdIRdBjJjDjS7967D_J1GOkTtOeanMUFs09_ESeeqJsf1eKJh9oafL4wzKWJr6HZvQgjrHRMsMae8BOw1m7V5HbO-31I8Iwnc3ItJpl3Y_D63PR9lQZSAg5bVLVUiIQkyt2j8Iwd9n6mqd2x3bnw" alt="This image has an empty alt attribute; its file name is vlFqoLIIafApCu6VJ5Zj3MSMTlUFCQh92VLBVqgdIRdBjJjDjS7967D_J1GOkTtOeanMUFs09_ESeeqJsf1eKJh9oafL4wzKWJr6HZvQgjrHRMsMae8BOw1m7V5HbO-31I8Iwnc3ItJpl3Y_D63PR9lQZSAg5bVLVUiIQkyt2j8Iwd9n6mqd2x3bnw" width="350" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selection of the date has a concurrent action of calling a Data Flow which gets all available time slots for that particular day. We use the “&lt;strong&gt;Get Free Slots”&lt;/strong&gt;  Data Flow, which will extract information from Google Calendar. See the screenshot for the variables to update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yKwSHqlr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/Q47wTOYqyjrsU-fyXXekJ45C7Q_9svVBU_mXOzDAXjtzK1vDGniK838Mc7bjWd8mBvzZXCSQuX9gMKKs9DbjIF9N39q-AvM0Ic_iIdBbdQvJpMVS-UgbtcZBL0xszwql1d8qPGtkMsNA8FRzf9r7a4ov1Ra83pf4KgUgpmOoKfXUXN0ZFQFEy0hkqw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yKwSHqlr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/Q47wTOYqyjrsU-fyXXekJ45C7Q_9svVBU_mXOzDAXjtzK1vDGniK838Mc7bjWd8mBvzZXCSQuX9gMKKs9DbjIF9N39q-AvM0Ic_iIdBbdQvJpMVS-UgbtcZBL0xszwql1d8qPGtkMsNA8FRzf9r7a4ov1Ra83pf4KgUgpmOoKfXUXN0ZFQFEy0hkqw" alt="This image has an empty alt attribute; its file name is Q47wTOYqyjrsU-fyXXekJ45C7Q_9svVBU_mXOzDAXjtzK1vDGniK838Mc7bjWd8mBvzZXCSQuX9gMKKs9DbjIF9N39q-AvM0Ic_iIdBbdQvJpMVS-UgbtcZBL0xszwql1d8qPGtkMsNA8FRzf9r7a4ov1Ra83pf4KgUgpmOoKfXUXN0ZFQFEy0hkqw" width="721" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The variable &lt;code&gt;session.availability&lt;/code&gt; will have all the time slot data which will populate the buttons in the Container List control. The application will show a list of buttons with the available time slots.&lt;/p&gt;

&lt;p&gt;Once a time slot is selected and confirmed via the Confirm Button control, the application will move to the next Web Page “Enter Details and Confirm”.&lt;/p&gt;

&lt;p&gt;On the second page, “Enter Details and Confirm”, we will create a scheduled event form where a user can create a meeting, add guests to the email guest list, add any notes to the meeting and then schedule the appointment. We will be adding another set of controls: Container, Label, Text Input, Text Area Input and Button. Make sure you label all the controls appropriately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mS60MyR---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/tsdcEYQHaylXYaqmxi3NTHFPNUKGkAEGTU3DOuQhFycW4so5zBG9dpltdb07AHH80TFUkyHC6R-yhycXBSegg8XNJhlq-m9anZPhieYj6wevL2Se1nCsspSYRVmGVbfvB8JHV12-RtO1zPBvfMHAlJ8tZ7hPZAPdZ_xZLCFhyk8aawGh7GUKa3wBNQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mS60MyR---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/tsdcEYQHaylXYaqmxi3NTHFPNUKGkAEGTU3DOuQhFycW4so5zBG9dpltdb07AHH80TFUkyHC6R-yhycXBSegg8XNJhlq-m9anZPhieYj6wevL2Se1nCsspSYRVmGVbfvB8JHV12-RtO1zPBvfMHAlJ8tZ7hPZAPdZ_xZLCFhyk8aawGh7GUKa3wBNQ" alt="This image has an empty alt attribute; its file name is tsdcEYQHaylXYaqmxi3NTHFPNUKGkAEGTU3DOuQhFycW4so5zBG9dpltdb07AHH80TFUkyHC6R-yhycXBSegg8XNJhlq-m9anZPhieYj6wevL2Se1nCsspSYRVmGVbfvB8JHV12-RtO1zPBvfMHAlJ8tZ7hPZAPdZ_xZLCFhyk8aawGh7GUKa3wBNQ" width="432" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Schedule Event button action unifies all the inputs from the initial page and current page via the “Schedule Event Data Flow” in order to create a Google Calendar Event with a Zoom Meeting. See the screenshot below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--36-S7rUF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/yhvNr8BYCMtG-Ej0KYal3JEQWCS40885UsMAm4cpMLK53Ss4uTovNA4hJ1UuNGusBvy-_KfjUwyEjSs0yH9Ec3ZP5wNt7YD1LZqqLm1i3UznUZT6Vv2051IGlNgIrScezGTAlgRXp3Iu_el271znYmUof79ExB2diGV5FCwi7yH5xghRc8G0b4uWBA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--36-S7rUF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/yhvNr8BYCMtG-Ej0KYal3JEQWCS40885UsMAm4cpMLK53Ss4uTovNA4hJ1UuNGusBvy-_KfjUwyEjSs0yH9Ec3ZP5wNt7YD1LZqqLm1i3UznUZT6Vv2051IGlNgIrScezGTAlgRXp3Iu_el271znYmUof79ExB2diGV5FCwi7yH5xghRc8G0b4uWBA" alt="This image has an empty alt attribute; its file name is yhvNr8BYCMtG-Ej0KYal3JEQWCS40885UsMAm4cpMLK53Ss4uTovNA4hJ1UuNGusBvy-_KfjUwyEjSs0yH9Ec3ZP5wNt7YD1LZqqLm1i3UznUZT6Vv2051IGlNgIrScezGTAlgRXp3Iu_el271znYmUof79ExB2diGV5FCwi7yH5xghRc8G0b4uWBA" width="741" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the event is scheduled, the user is sent to the Thank You page.&lt;/p&gt;

&lt;p&gt;On the final page, a Thank you page, add a label which thanks the user for scheduling an appointment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B308HiPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/1Drc1ohl9WX_JxYxHR1ngelBocIyK8Uzsz9RG4pf-aZFmwHq21fOPlnLCIp0ysxOGqPYISJ6savcNjyzB0dUUPl9SFmFYl4YULNPcXFQea9kFh41RWSiUsAztVYpo4vTaMM-P6QpkBKuwXEXTF-3BTyTB7B_5nmelIsv2uYw5fDQfE8-L14GJZGSSg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B308HiPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/1Drc1ohl9WX_JxYxHR1ngelBocIyK8Uzsz9RG4pf-aZFmwHq21fOPlnLCIp0ysxOGqPYISJ6savcNjyzB0dUUPl9SFmFYl4YULNPcXFQea9kFh41RWSiUsAztVYpo4vTaMM-P6QpkBKuwXEXTF-3BTyTB7B_5nmelIsv2uYw5fDQfE8-L14GJZGSSg" alt="This image has an empty alt attribute; its file name is 1Drc1ohl9WX_JxYxHR1ngelBocIyK8Uzsz9RG4pf-aZFmwHq21fOPlnLCIp0ysxOGqPYISJ6savcNjyzB0dUUPl9SFmFYl4YULNPcXFQea9kFh41RWSiUsAztVYpo4vTaMM-P6QpkBKuwXEXTF-3BTyTB7B_5nmelIsv2uYw5fDQfE8-L14GJZGSSg" width="432" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Hopefully this walkthrough has been a helpful guide on how to create a calendly clone on your own.&lt;/p&gt;

&lt;p&gt;Even this build has room to add additional functionality! You could upload additional documents for the meeting to the calendar invite via the File Upload control. You could change the video conference link by creating a new API integration with Microsoft Teams or any other video conferencing service. Try adding new features of your own and let us know what you build!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>lowcode</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cat Trophy Kingdom: Building a video game with Airkit’s low-code platform</title>
      <dc:creator>Chandra</dc:creator>
      <pubDate>Tue, 11 Oct 2022 22:09:51 +0000</pubDate>
      <link>https://dev.to/airkit/cat-trophy-kingdom-building-a-video-game-with-airkits-low-code-platform-5dlh</link>
      <guid>https://dev.to/airkit/cat-trophy-kingdom-building-a-video-game-with-airkits-low-code-platform-5dlh</guid>
      <description>&lt;h1&gt;
  
  
  Cat Trophy Kingdom: Building a video game with Airkit’s low-code platform
&lt;/h1&gt;

&lt;p&gt;A few weeks ago, in preparation for releasing version 18.1 of Airkit, some coworkers and I were stress-testing the new animation functionality. We started with the basics, things we imagined might be among the most common use cases: building out a customized progress bar, associating an animation with the appearance of a warning label so as to draw more attention to it. Then, with the cadence of a joke, someone asked, “Can we animate a chicken crossing the road?”&lt;/p&gt;

&lt;p&gt;The answer, as it turned out, was that we could. Easily. It took less than ten minutes and required only two assets: a JPEG of a road at dusk, and a GIF of a dancing chicken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FFlxJudu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/bhgJ8FclWl008vuHVWDLiO33t80A26UHh_deiOSbj6gpkweYYuZzq2geYD3gHfAGspErVKWlf_SytlIp4VbVJz2XrjXX77y7SF8FcAbCwIgPJ60XYg0xOLrew0aaAT2BhSCkAyP1IAELUcqo6yiISlSCcheeKmTxKypbKHzB34V66BTQKkTiDMoZ6w" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FFlxJudu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/bhgJ8FclWl008vuHVWDLiO33t80A26UHh_deiOSbj6gpkweYYuZzq2geYD3gHfAGspErVKWlf_SytlIp4VbVJz2XrjXX77y7SF8FcAbCwIgPJ60XYg0xOLrew0aaAT2BhSCkAyP1IAELUcqo6yiISlSCcheeKmTxKypbKHzB34V66BTQKkTiDMoZ6w" alt="A gif" width="772" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was in love. In the span of less than ten minutes, this road-crossing chicken had become my muse, for it made me realize two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Airkit’s animation functionality can be combined with GIFs to create the illusion of complex motion with very little effort.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you can animate a chicken moving in one direction, you can apply the same principles to move &lt;em&gt;any&lt;/em&gt; character in &lt;em&gt;any&lt;/em&gt; direction.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And once I had realized those two things, the conclusion was inevitable. I would use Airkit to build a video game.&lt;/p&gt;

&lt;p&gt;In around 48 hours, I had put together &lt;a href="https://app.airkit.com/cat-trophy-kingdom"&gt;Cat Trophy Kingdom&lt;/a&gt;, a short CRPG (Computer Role-Playing Game) where you earn the crown of Cat King by knowing trivia about how to build in Airkit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--osO74t7T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/qK1eDVbygEaQeOZ9-fzKiiR2w8OXkyWM0gtAV90AspkABAXzynGgd3tSmQQ4CT7ISB0ryQ4rgwId7nXyNjY_zJPJLgkEE0oGka1aesziveTyRwVykbLBMdOxAQTInDl2ro49_CcUDNb21R25p9pW0sYhLq_P-muZglolp3zqDgkxIwPim5sfMoS1ew" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--osO74t7T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/qK1eDVbygEaQeOZ9-fzKiiR2w8OXkyWM0gtAV90AspkABAXzynGgd3tSmQQ4CT7ISB0ryQ4rgwId7nXyNjY_zJPJLgkEE0oGka1aesziveTyRwVykbLBMdOxAQTInDl2ro49_CcUDNb21R25p9pW0sYhLq_P-muZglolp3zqDgkxIwPim5sfMoS1ew" alt="" width="810" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Pictured: A brief snippet of Cat Trophy Kingdom. Click &lt;a href="https://app.airkit.com/cat-trophy-kingdom"&gt;here&lt;/a&gt; to play!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is absolutely not the sort of thing Airkit was optimized to build, and so while I did have to implement a few hacky work-arounds, I was overall pleasantly surprised by how quickly I was able to throw together something so far beyond the scope of CX automation.&lt;/p&gt;

&lt;p&gt;Let me tell you about how I did it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building the Castle
&lt;/h1&gt;

&lt;p&gt;When I first started building, I had only a vague idea of how I wanted the game to go, and I wasn’t entirely sure what would be feasible to complete in the 48-hour period I had given myself. I started with the basics: the game needed a protagonist.&lt;/p&gt;

&lt;p&gt;After considering the constraints I was working within, not least of which was time, I decided to make a GIF of a little yellow cat held up by balloons. Out-of-the box animation in Airkit is smooth thanks to the automatic incorporation of easing, which looks good in combination with gentle bobbing. A floating means of transportation also meant that I didn’t need to worry about timing and configuring a complex walking cycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OeGTZJtS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/neUKeijjx951Ml7sGCwry0riR7PTDTOUYrpkTz6vrH7AXhf4u2B8kxGNAJQ4GBPJslgXZxTxhlTl8ThAkK6Y5PTPC23ZFDOu3ufLp7HkH_oOgSekofNBfhna8tw-L0MajIghaVBr22fa0fOHI87G-wj6ZiljRRxZjjfSmy8_vR4wkcl0vJyy-CcfRA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OeGTZJtS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/neUKeijjx951Ml7sGCwry0riR7PTDTOUYrpkTz6vrH7AXhf4u2B8kxGNAJQ4GBPJslgXZxTxhlTl8ThAkK6Y5PTPC23ZFDOu3ufLp7HkH_oOgSekofNBfhna8tw-L0MajIghaVBr22fa0fOHI87G-wj6ZiljRRxZjjfSmy8_vR4wkcl0vJyy-CcfRA" alt="" width="880" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Almost immediately, I began to mentally refer to this cat as the “PC,” which stood simultaneously for “Player Character” and “Player Cat.”&lt;/p&gt;

&lt;p&gt;Now that I had my PC, I needed to create a space for them to explore. I’d already decided that I wanted to set the game in a castle, so I found a picture of some masonry that could pass as a castle floor. This, I set as the background for the &lt;a href="https://support.airkit.com/reference/container-web-control"&gt;Container&lt;/a&gt; that would hold all the furniture and characters that would exist in my digital castle, starting with my PC. For the sake of organization and scalability, I put the &lt;a href="https://support.airkit.com/reference/image-web-control"&gt;Image Web Control&lt;/a&gt; containing the GIF of my PC in its own Container, which nested under the broader Container encompassing the whole castle floor. It was to this sub-Container that I would apply animations. I was kicking around the idea of having visible objects float alongside the PC under certain circumstances, and I didn’t want to have to animate them individually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p7NiJK3P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/L0N_MDz1kBCI-7yDNzQ0Tkc9i7YSJKzSyRqOtceWR18eoeyeo3UXvJpLdrRoLbBJtQhsv2TUhPw7veX7qe4wWI1lmQG-N27g-lT_OmsXwCs7SV3Yb2_ITLCkvcl3Yb7P5O3MyMLuMnKHtzQsLnch012rPuIw0IPid5ALyNU48ZnFwUHE0bvXKAxXPw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p7NiJK3P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/L0N_MDz1kBCI-7yDNzQ0Tkc9i7YSJKzSyRqOtceWR18eoeyeo3UXvJpLdrRoLbBJtQhsv2TUhPw7veX7qe4wWI1lmQG-N27g-lT_OmsXwCs7SV3Yb2_ITLCkvcl3Yb7P5O3MyMLuMnKHtzQsLnch012rPuIw0IPid5ALyNU48ZnFwUHE0bvXKAxXPw" alt="" width="880" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PC needed a way to move about the castle floor, not in a single direction, but four: up, down, left, and right. These would dictate movement along two dimensions: the vertical and the horizontal. I defined two variables to keep track of the cat’s placement along these dimensions: &lt;strong&gt;x&lt;/strong&gt;, to track distance in pixels from the leftmost border, and &lt;strong&gt;y&lt;/strong&gt;, to track distance in pixels from the upper border. So that their values could be easily changed by simple addition and subtraction, both of these variables were &lt;code&gt;Numbers&lt;/code&gt;. (This whole setup was based off of the standard &lt;a href="https://en.wikipedia.org/wiki/Cartesian_coordinate_system"&gt;Cartesian plane&lt;/a&gt;, with the orientation of the y axis reversed to avoid having to work with negative margin values.)&lt;/p&gt;

&lt;p&gt;Upon updating the Airkit platform to v18.1, animations are incorporated into the preexisting UI of the platform by tying them to the &lt;a href="https://support.airkit.com/reference/common-style-properties-of-web-controls"&gt;styling properties&lt;/a&gt; of &lt;a href="https://support.airkit.com/reference/web-controls-overview"&gt;Web Controls&lt;/a&gt;. While styling properties are defined statically by default, they can also be defined in terms of variables, and changes to the values of such variables are reflected as changes in the appearance of the Web Control. Applying animations to the variable styling properties smoothens the transition. The Container that held my PC, then, would have varying values of the left and top margins, which would change according to the values of &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt; respectively. (Because I had defined &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt; as &lt;code&gt;Numbers&lt;/code&gt;, I needed to use the &lt;a href="https://support.airkit.com/reference/format_number"&gt;FORMAT_NUMBER&lt;/a&gt; function to convert them into a string the margin expression could parse. The left margin was defined as &lt;code&gt;“{{FORMAT_NUMBER(x, “”)}}px”&lt;/code&gt;, and the upper margin was defined as &lt;code&gt;“{{FORMAT_NUMBER(y, “”)}}px”&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;I then animated changes to the margin so that the transition between different margin values would take place over the span of a second:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EjQmJd9E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/wqvNHqsrF4je8KAbtRwDmnm8SJVb02iNs-mYLYZZ250RfqLbJaPoAUpPFTSdSJUEe3hd4COGGRbTi9x5dXn7MubzNIJ2VgXBEZqUSy5vD3YaRA2akJEJN4ezfFe78U5hCEsS5BcQD-xsnbOK7_wtq30udB6R9xmIzgZMR_tAhMyJFPNcBIZrAwybEg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EjQmJd9E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/wqvNHqsrF4je8KAbtRwDmnm8SJVb02iNs-mYLYZZ250RfqLbJaPoAUpPFTSdSJUEe3hd4COGGRbTi9x5dXn7MubzNIJ2VgXBEZqUSy5vD3YaRA2akJEJN4ezfFe78U5hCEsS5BcQD-xsnbOK7_wtq30udB6R9xmIzgZMR_tAhMyJFPNcBIZrAwybEg" alt="" width="796" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The values of &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt; would be changed by pressing &lt;a href="https://support.airkit.com/reference/button-web-control"&gt;Buttons&lt;/a&gt;, which would trigger the &lt;a href="https://support.airkit.com/reference/the-set-variable-action"&gt;Set Variable Action&lt;/a&gt; to change the value of &lt;strong&gt;x&lt;/strong&gt; or &lt;strong&gt;y&lt;/strong&gt; as appropriate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sk0y2OvX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/Gjbj-p_g2aclFEpyJ9IPalKmCiNTfRIcn4o6rf2b_6Xn10IQOljSch4-f9jLl_DzOULbyGTgIMNTGnTf97mo85IcH-I7sDChTeUX1ku3Cn8F_maQJ0wUBZ1AmjEmEWCMcbU5ODM2wuRKhvLjgwgWHTTSWDNrndgqn6ok5T4KiVeQ_l5PSLdi4E4Lpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sk0y2OvX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/Gjbj-p_g2aclFEpyJ9IPalKmCiNTfRIcn4o6rf2b_6Xn10IQOljSch4-f9jLl_DzOULbyGTgIMNTGnTf97mo85IcH-I7sDChTeUX1ku3Cn8F_maQJ0wUBZ1AmjEmEWCMcbU5ODM2wuRKhvLjgwgWHTTSWDNrndgqn6ok5T4KiVeQ_l5PSLdi4E4Lpg" alt="" width="880" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The values of &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt; would change by adding or subtracting numbers to or from their initial value, so once the Buttons were configured, I set up the initialization of &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt;. Upon navigating to the &lt;a href="https://support.airkit.com/reference/web-page"&gt;Web Page&lt;/a&gt; on which I was building my castle, two Set Variable Actions would be fired, setting first &lt;strong&gt;x&lt;/strong&gt; to 0 and then &lt;strong&gt;y&lt;/strong&gt; to 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ztm9w09M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/3L8vD2aXmMqoUV-yXxZAOU_fozEgLqmqP4MgWfopIaEKAntDE2OVi-hOXtZ-YaActqzWJ82E7SycVyT4MhEFlXXKcOaKiIHXgVfLdYUWy5YOR8aaIS5qeC2BtgXleVS1Tb1k53lZlo0K9KDxoP52mrsrgBejsjHcOvqc-WW8ARaZ2pI7nuc7QLchwg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ztm9w09M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/3L8vD2aXmMqoUV-yXxZAOU_fozEgLqmqP4MgWfopIaEKAntDE2OVi-hOXtZ-YaActqzWJ82E7SycVyT4MhEFlXXKcOaKiIHXgVfLdYUWy5YOR8aaIS5qeC2BtgXleVS1Tb1k53lZlo0K9KDxoP52mrsrgBejsjHcOvqc-WW8ARaZ2pI7nuc7QLchwg" alt="" width="322" height="646"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Pictured: A fancy cat doing a fancy dance.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By this point, I had built out something only marginally more complicated than the chicken crossing the road, and it was becoming clear that I was going to have to find ways around Airkit’s core assumptions if I wanted to get much further.&lt;/p&gt;

&lt;p&gt;Airkit apps are designed to be easily accessible on screens of any size, browser or mobile. Out of the box, Web Controls don’t have a set pixel size; they scale automatically to account for the size of each user’s screen. When defining dimensions within styling properties, Airkit parses input given in either pixels or percentages. “50px” means fifty pixels, and “50%” means fifty percent of the Container the Web Control is nested under, which by default depends on the size of the screen being used to view the app. In most of the common use cases, the things Airkit was optimized to build – form digitization, call deflection – it’s considered best practice to define lengths in terms of percentages when possible, allowing the UI to appear standardized regardless of screen size.&lt;/p&gt;

&lt;p&gt;As you may have already noticed, I was not doing this. I was already defining my PC’s movement in terms of pixels, not percentages. To better create the illusion of various images interacting with each other, I wanted fine control of each image’s location down to the pixel.&lt;/p&gt;

&lt;p&gt;So I redefined the size of the castle floor Container, overwriting the defaults that would adjust the size to fit the screen it was displayed on. This meant I had to pick, then and there, what sort of screen ought to be used to play the game, and I decided to make a computer game, not a mobile-friendly experience. I set the size of my castle floor Container to exactly 1200 pixels by 900 pixels and tinkered around with the &lt;a href="https://support.airkit.com/docs/page-layout"&gt;Page Layout&lt;/a&gt; so that the layout would remain the same regardless of screen size. This would render the game effectively unplayable if someone tried to access it from their phone, but that was a sacrifice I was willing to make.&lt;/p&gt;

&lt;p&gt;This also meant I had to put the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons in a Container and adjust their placement so that they were more obviously visible on a computer screen.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ejmrk_gm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/-RgpPg-Ym_LPIL0GifATn6AVv5qdXjW84Aci7e61KSi6iR0m_1VeCnUG_kXgWlqAgaUtG27K0oiVo-kwQwGQDEVkjkQ2zmBYGpDjXmb5dJDkd2YAGikMY6c6-apqCnPOLQCeZ1qS8lng48quPQu3A-5Okv-xkZwkK4BZxAZpPX573SwSqJ5SRpdtsw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ejmrk_gm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/-RgpPg-Ym_LPIL0GifATn6AVv5qdXjW84Aci7e61KSi6iR0m_1VeCnUG_kXgWlqAgaUtG27K0oiVo-kwQwGQDEVkjkQ2zmBYGpDjXmb5dJDkd2YAGikMY6c6-apqCnPOLQCeZ1qS8lng48quPQu3A-5Okv-xkZwkK4BZxAZpPX573SwSqJ5SRpdtsw" alt="" width="806" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(This was my first time &lt;a href="https://support.airkit.com/reference/common-style-properties-of-web-controls"&gt;changing the position of a Container from Relative to Absolute&lt;/a&gt;, but it would not be my last.)&lt;/p&gt;

&lt;p&gt;My PC now had a defined and finite castle floor to explore. Pressing the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons would move the PC some number of pixels in the desired direction. How far each Button would move the PC was entirely arbitrary, but as long as the size of the change stayed consistent, it imposed a conceptual grid structure on occupiable spaces. The PC would only be able to reach spaces divisible by the number of pixels it could move at a time.&lt;/p&gt;

&lt;p&gt;Here is the convention I settled on. The PC itself would occupy a 100 pixel by 100 pixel Container, which would move in 100-pixel steps. The coordinates “defining” the position of the PC would be defined by the pixel at the upper left of the 100 pixel by 100 pixel Container. For instance, given &lt;strong&gt;x&lt;/strong&gt; = 0 and &lt;strong&gt;y&lt;/strong&gt; = 0, the PC would appear at the upper left corner, apparently filling up the space defined by all values of &lt;strong&gt;x&lt;/strong&gt; between 0 and 99, and all values of &lt;strong&gt;y&lt;/strong&gt; between 0 and 99.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y-dj6-l2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/AW4oKqSbM638J1vS1P02XcZlJMdk0Gv1PcK-YNguhcuxqYewGazg57Es-qMkT3uF1X7OqbWmCVovw5fHojmvNsFvbKtR7KZnTBX1muNqXC6r0axLyfp6hEBf2SYyFOxC9Rk63SdoB-Avfz1YXXI6Q7Ha37s2JiXADl9hlqzp8l82wwxjhyW_yVS9ig" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y-dj6-l2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/AW4oKqSbM638J1vS1P02XcZlJMdk0Gv1PcK-YNguhcuxqYewGazg57Es-qMkT3uF1X7OqbWmCVovw5fHojmvNsFvbKtR7KZnTBX1muNqXC6r0axLyfp6hEBf2SYyFOxC9Rk63SdoB-Avfz1YXXI6Q7Ha37s2JiXADl9hlqzp8l82wwxjhyW_yVS9ig" alt="" width="880" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Pictured: The grid as it exists in my imagination.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Upon saving and previewing my progress, I was delighted to see my PC move about as intended. However, the lack of limitations surrounding the castle floor quickly became apparent: pressing the &lt;strong&gt;Up&lt;/strong&gt; Button too many times sent my PC floating up and away into the limitless void, never to be seen again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yot9LRME--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/MYR-jev8cQt_bF2_6u4ds5FfX5ob3qzm7f1O7_5o9Be3OOjobjamV2HtInuOH_Qg6PdSmeoxI8JZNgrw2Di6PpcFvE0c8Qwop4EO4gyy-U-1YPs6vGk4-Pqv-VnqsBiaCVKSswfWJKbWTknKr6aIn1FogeVVM0K79ALUaGYy2jHqbjZ4nvPTMoj5AQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yot9LRME--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/MYR-jev8cQt_bF2_6u4ds5FfX5ob3qzm7f1O7_5o9Be3OOjobjamV2HtInuOH_Qg6PdSmeoxI8JZNgrw2Di6PpcFvE0c8Qwop4EO4gyy-U-1YPs6vGk4-Pqv-VnqsBiaCVKSswfWJKbWTknKr6aIn1FogeVVM0K79ALUaGYy2jHqbjZ4nvPTMoj5AQ" alt="" width="880" height="667"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Unless I pressed the &lt;strong&gt;Down&lt;/strong&gt; Button an equally ludicrous number of times.)&lt;/p&gt;

&lt;p&gt;I needed to impose further restrictions on the spaces my PC could occupy: I did not want them to be able to leave the castle floor, so I decided to create a new &lt;a href="https://support.airkit.com/reference/udf"&gt;UDF&lt;/a&gt; (“User Defined Function”) to check if a proposed destination was within the established parameter. The UDF, designated &lt;code&gt;ISINSIDE#USER_FUNCTION&lt;/code&gt;, took two &lt;code&gt;Numbers&lt;/code&gt;, &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt;, as input, used the out-of-box &lt;a href="https://support.airkit.com/reference/if"&gt;IF&lt;/a&gt; function to return the boolean &lt;code&gt;TRUE&lt;/code&gt; if &lt;strong&gt;x&lt;/strong&gt; and &lt;strong&gt;y&lt;/strong&gt; described a position inside the castle, and returned &lt;code&gt;FALSE&lt;/code&gt; if otherwise:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--laHzQ-G_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/nCzbKhA1fJwCyDhbp3-bQB6mewzrEPLM8hWhbFbYDXdiJ_lIOas_2sGRztRqhbimaDZagNEJQ4_27Tpu9ji1ejUYjkEq2TeoZrMHUtUOOPQKhw3zdvVMrGcG9lTROqPqL47WFs5wJZhtKvJosA8V-YtpTjQWyiC89iQi8XbYIXTHjxDIQ-7qvElJJA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--laHzQ-G_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/nCzbKhA1fJwCyDhbp3-bQB6mewzrEPLM8hWhbFbYDXdiJ_lIOas_2sGRztRqhbimaDZagNEJQ4_27Tpu9ji1ejUYjkEq2TeoZrMHUtUOOPQKhw3zdvVMrGcG9lTROqPqL47WFs5wJZhtKvJosA8V-YtpTjQWyiC89iQi8XbYIXTHjxDIQ-7qvElJJA" alt="" width="880" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By making this a UDF, I was able to reuse it easily, and I incorporated it into the Action Chains associated with the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons. Now, before resetting the value of either &lt;strong&gt;x&lt;/strong&gt; or &lt;strong&gt;y&lt;/strong&gt; (as appropriate), each would run a &lt;a href="https://support.airkit.com/reference/the-condition-action"&gt;Conditional Action&lt;/a&gt; to check if the new coordinates were inside of the castle. If they were, the cat would move as asked. If not, the cat would stay still.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fvGsd2w8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/nhAKSHxyCGed0GCWoUi9GtjPjq2c_emv_vqF_DVbDeJXlCou0QlhAOrtc3bK0mlCPQ0R6MY3UZwVyUKFlByvy2flB5vdKSoPVepOEKmAfDPAuRXfShAd2XYKzeNXd-oFaOSF1PmHMJazQo48XewceUuBxN-FGdxUVDk3rm25snw-2r98EX7jt_dnfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fvGsd2w8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/nhAKSHxyCGed0GCWoUi9GtjPjq2c_emv_vqF_DVbDeJXlCou0QlhAOrtc3bK0mlCPQ0R6MY3UZwVyUKFlByvy2flB5vdKSoPVepOEKmAfDPAuRXfShAd2XYKzeNXd-oFaOSF1PmHMJazQo48XewceUuBxN-FGdxUVDk3rm25snw-2r98EX7jt_dnfQ" alt="" width="880" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This accomplished the illusion of the cat being unable to pass through the implied walls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vQqM9tEg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/jo-SLlKScg3F3KHI3hEkzjuiAmOUo30q_VbQjW6UWuYB2vRAuw5MTc-JQi4w1Q62-PZ_dty3L-o0cwGXZsmu1ORUaIEEu7yZMnDYUp7DiRQ8oH9wFUIjvoR-wcJRuboWfSFKoT4LNMDP-NxPXLAyZ-RntEqNPfNwm8SVYCI8HTUInRWeku9cjhF9zw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vQqM9tEg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/jo-SLlKScg3F3KHI3hEkzjuiAmOUo30q_VbQjW6UWuYB2vRAuw5MTc-JQi4w1Q62-PZ_dty3L-o0cwGXZsmu1ORUaIEEu7yZMnDYUp7DiRQ8oH9wFUIjvoR-wcJRuboWfSFKoT4LNMDP-NxPXLAyZ-RntEqNPfNwm8SVYCI8HTUInRWeku9cjhF9zw" alt="" width="810" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So now I had my PC, and the PC had a room to explore. Now was time to fill the room with characters and furniture for my PC to interact with.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interacting with Characters and Furniture
&lt;/h1&gt;

&lt;p&gt;The process of building my castle floor had solidified two key ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;My 1200 pixel by 900 pixel castle floor was also a 12 by 9 grid, in which my PC could occupy exactly one square at a time (and each square was defined by the coordinates of the pixel in the top left corner). The same principles could be applied to define the locations of furniture and other characters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keeping track of the location of each character or piece of furniture could be done via UDF, and each UDF could be used in an additional IF ELSE statement associated with the Conditionals tied to the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first character I wanted to establish was the Cat King, as the PC’s interactions with him would make up a core part of the game. I whipped up a sprite for him, which I confined to its own 100 pixel by 100 pixel Container. His placement on the board was defined using the same conventions as I had applied to my PC, only no variables were required:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pG_JyD6R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/ACv8MxKPFm4G78_Nw1FUQwxIloOG6ECmKauVMKGVCQ5vLzNJ-Fn9ZFo9wbZUUTQbYeVgRJVv-WQbT0OOHdPtZ3xOI9002XlAM1JuUErF-3OmI2WWNDAPng_Bc9gAtBkx2MTJ6IEZh-H6UrLU5WzrpV44RZ3OiaC--Zhohr4OfieR4o2uotW5IR9h" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pG_JyD6R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/ACv8MxKPFm4G78_Nw1FUQwxIloOG6ECmKauVMKGVCQ5vLzNJ-Fn9ZFo9wbZUUTQbYeVgRJVv-WQbT0OOHdPtZ3xOI9002XlAM1JuUErF-3OmI2WWNDAPng_Bc9gAtBkx2MTJ6IEZh-H6UrLU5WzrpV44RZ3OiaC--Zhohr4OfieR4o2uotW5IR9h" alt="" width="858" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This placed the Cat King in the location described by &lt;strong&gt;x&lt;/strong&gt; = 800 and &lt;strong&gt;y&lt;/strong&gt; = 100.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J0-5iR8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/NK9FMPQhcEpFFafABDuNxub_FIS4DpGMlbwzdDgJxK5twgPo7Y1GJr81na0PZ3wWcV-wkMTaICM7mO4m8k-CST2Madzw6yEhNGNgLF2Z4pSTc8lr081kNCwJbWyGO2hm-n858tWnfbDH5aB_ZJPv9TVrTtv2bXmBqIgrFOg6djMejTTIb00vzbR2Rw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J0-5iR8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/NK9FMPQhcEpFFafABDuNxub_FIS4DpGMlbwzdDgJxK5twgPo7Y1GJr81na0PZ3wWcV-wkMTaICM7mO4m8k-CST2Madzw6yEhNGNgLF2Z4pSTc8lr081kNCwJbWyGO2hm-n858tWnfbDH5aB_ZJPv9TVrTtv2bXmBqIgrFOg6djMejTTIb00vzbR2Rw" alt="" width="880" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Pictured: The placement of the Cat King in my imagination.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I didn’t want my PC to be able to enter this occupied space, so I applied a similar strategy to the one I had used to prevent my PC from leaving the castle floor.&lt;/p&gt;

&lt;p&gt;First, I created a new UDF, called &lt;code&gt;ONCATKING&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V57ybTIP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/PlP7BrwYnYhwv3Uzbo9KRQeIptduAWlinDVJR-Zdwg2Gjr2Fx5kBt9zP_HB4D1wXzo0E6LzfT1zLKT_1VwmlCN5yKMmYtcedto6DLyBRYVJcxN-oogjuMHHuYqFjP0utFl0E6w02fA7W7v4c5Sa_ZOA_Z8vQC9v_W8UL5BTOY3l3Agf9hTu8lz3f" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V57ybTIP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/PlP7BrwYnYhwv3Uzbo9KRQeIptduAWlinDVJR-Zdwg2Gjr2Fx5kBt9zP_HB4D1wXzo0E6LzfT1zLKT_1VwmlCN5yKMmYtcedto6DLyBRYVJcxN-oogjuMHHuYqFjP0utFl0E6w02fA7W7v4c5Sa_ZOA_Z8vQC9v_W8UL5BTOY3l3Agf9hTu8lz3f" alt="" width="880" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, in the Action Chains associated with each of my &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons, I added an IF ELSE branch and used my new UDF to trigger different behavior in cases where the PC tried to occupy the same space as the Cat King. Now, before resetting the value of either &lt;strong&gt;x&lt;/strong&gt; or &lt;strong&gt;y&lt;/strong&gt; (as appropriate), it would not only check to see if the new coordinates would be inside the castle, if they were, it would also check to see if the new coordinates would put the PC in the same square as the Cat King.&lt;/p&gt;

&lt;p&gt;And if so, I wanted to &lt;a href="https://support.airkit.com/reference/the-open-modal-action"&gt;open a modal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Up until this point, I’d been working within a single Web Page, but now I needed to make a new Web Flow, which I would open as a modal in order to bring up the Cat King’s dialogue and allow the player to select how they wanted to reply. In my early iterations, the Cat King’s dialogue was a simple &lt;a href="https://support.airkit.com/reference/label-web-control"&gt;Label&lt;/a&gt;, and the dialogue options were simple Buttons, which might &lt;a href="https://support.airkit.com/reference/the-close-modal-action"&gt;close the modal&lt;/a&gt; or &lt;a href="https://support.airkit.com/reference/navigate-to-web-page-action"&gt;navigate to another Web Page&lt;/a&gt; depending on the nature of the reply. For a bit of polish, I shortly after incorporated additional styling components, such as a drawing of the Cat King’s face, and a talk balloon gif from &lt;a href="https://giphy.com"&gt;Giphy&lt;/a&gt;. After nesting some additional Containers and playing around with Airkit’s out-of-the-box &lt;a href="https://support.airkit.com/reference/common-style-properties-of-web-controls"&gt;styling properties&lt;/a&gt;, I found a layout I liked, which I would later re-use (by copying and pasting) when creating additional character interactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZscUmvas--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/CsZii_OCSgiy8Ii8krmtNVUeiKzQqCPl6tCqfmWu6HES70XcuVDpD2LjddIFioZ4ce1QsJsfv7kE-srSH1JXbsYSshX0Le8aKgsB6lbQcJNFELV9EXhkXqhmnn6SoeWmYl-CXf-9qECShvHM89nKOtaMK_TZlT20kDVNyQWELrUkEOQxbUo104XmqA" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZscUmvas--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/CsZii_OCSgiy8Ii8krmtNVUeiKzQqCPl6tCqfmWu6HES70XcuVDpD2LjddIFioZ4ce1QsJsfv7kE-srSH1JXbsYSshX0Le8aKgsB6lbQcJNFELV9EXhkXqhmnn6SoeWmYl-CXf-9qECShvHM89nKOtaMK_TZlT20kDVNyQWELrUkEOQxbUo104XmqA" alt="" width="518" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I added more possible character (and furniture!) interactions to my game, I repeated this process in at least a dozen different forms. For each new interaction, I would place the sprite, make a UDF to test if the space was occupied by the sprite in question, and then I would incorporate the UDF into new IF ELSE statements in the Conditionals associated with the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons.&lt;/p&gt;

&lt;p&gt;And yes, these Action Chains did grow very, very long, very, very quickly.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v7ErNlIa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/giwae6bN5MNQ2d76EdQ-wUmSSAIq94JFc8VdMCbKPr9Qs1bHG4A3eKjnQuwA7pAdNeM7M6xRMtV9niSq8iKeB0atn4iFjL217_awRzI5dtMVp7Mp-G9Kl6TeggSnAtuU2RS3qsRkA7hopDDA3hDZdtH6pV8ca61jbI98vMv2VvynYX5XpXUMrJ2T3w" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v7ErNlIa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/giwae6bN5MNQ2d76EdQ-wUmSSAIq94JFc8VdMCbKPr9Qs1bHG4A3eKjnQuwA7pAdNeM7M6xRMtV9niSq8iKeB0atn4iFjL217_awRzI5dtMVp7Mp-G9Kl6TeggSnAtuU2RS3qsRkA7hopDDA3hDZdtH6pV8ca61jbI98vMv2VvynYX5XpXUMrJ2T3w" alt="" width="858" height="1266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Image Caption: This Action Chain is too long for my computer screen to even display all at once.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key insight: Using UDFs to keep track of occupied spaces was an even better idea than I first thought.
&lt;/h2&gt;

&lt;p&gt;When I first made a UDF to keep track of whether a proposed location was inside the castle, I had originally only been thinking to save myself from writing &lt;code&gt;x &amp;lt;  0  OR x &amp;gt;=  1200  OR y &amp;lt;  0  OR y &amp;gt;=  900&lt;/code&gt; four separate times (one for the Conditional associated with each of the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons). I made UDFs to keep track of each occupied space for the same reason.&lt;/p&gt;

&lt;p&gt;But as I placed more and more characters and pieces of furniture on the castle floor, I was sometimes struck by the urge to rearrange them, often for aesthetic purposes, more rarely to place the obstacles so that the way around them seemed less ambiguous. Regardless of why, moving the furniture meant both redefining the margins on the Container holding the furniture in question, and redefining the Conditional that managed the PC’s interaction with the furniture.&lt;/p&gt;

&lt;p&gt;And when I needed to do that, I was glad I had a UDF that served as the single source of truth for the location of an interaction. Changing the UDF so that it referenced the new location of the occupied space was all it took to update every relevant Conditional, across every Button.&lt;/p&gt;

&lt;h1&gt;
  
  
  Tracking Progress and Interactions
&lt;/h1&gt;

&lt;p&gt;Now I had built my castle and filled it with characters and furniture for my PC to interact with. There was one last key component of the game to work out: tracking the PC’s progress.&lt;/p&gt;

&lt;p&gt;Game-play-wise, the flow I had in mind was not complicated: the PC would talk to the Cat King, who would tell them to bring him a Cat Trophy. Then the PC would talk to the Court Mage, who would give them a riddle, and once the PC answered it correctly, they would get the Cat Trophy. Then the PC would bring the Cat Trophy to the King, winning the game.&lt;/p&gt;

&lt;p&gt;The same modal could not be opened every time the PC went to the location of the Cat King. One modal needed to open when the PC did not have the trophy, and a different modal needed to open when the PC did.&lt;/p&gt;

&lt;p&gt;I wound up tracking these sorts of interactions with &lt;code&gt;Booleans&lt;/code&gt;. For instance, I created a &lt;code&gt;Boolean&lt;/code&gt; variable called &lt;strong&gt;have_trophy&lt;/strong&gt;, which was initialized as &lt;code&gt;FALSE&lt;/code&gt; at the &lt;a href="https://support.airkit.com/docs/starting-events"&gt;start of the Journey&lt;/a&gt; but reset to &lt;code&gt;TRUE&lt;/code&gt; when the correct Button was pressed to answer the court mage’s riddle.&lt;/p&gt;

&lt;p&gt;More IF ELSE statements were added to the Conditionals associated with the &lt;strong&gt;Up&lt;/strong&gt;, &lt;strong&gt;Down&lt;/strong&gt;, &lt;strong&gt;Left&lt;/strong&gt;, and &lt;strong&gt;Right&lt;/strong&gt; Buttons, and some of the IF ELSE statements that I had already made grew more complicated. For instance, the Condition that needed to be met by the &lt;strong&gt;Left&lt;/strong&gt; Button in order to open the Cat King’s initial modal, where he told the PC to bring him a Cat Trophy, used to look only like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MPGG7YgH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/gZHLjxCrAeUcMuccnwMx4cRR83Nwwcnou4l7RauZSU8feAF0jZQtFRYnAtNKOyho_d2-AgjtuqU1ioLgmxsgvGv_qslUwlfW2gIBXJOH_94ZdLt-VwGllbphOte1BdEkc3GwcJ4ShL7o4ncSHS2LU3z_gyCi00dSbfg-fxY7NNhfTP-rcLQaRTmcHw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MPGG7YgH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/gZHLjxCrAeUcMuccnwMx4cRR83Nwwcnou4l7RauZSU8feAF0jZQtFRYnAtNKOyho_d2-AgjtuqU1ioLgmxsgvGv_qslUwlfW2gIBXJOH_94ZdLt-VwGllbphOte1BdEkc3GwcJ4ShL7o4ncSHS2LU3z_gyCi00dSbfg-fxY7NNhfTP-rcLQaRTmcHw" alt="" width="690" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and now it looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nSuZMPYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/IAqYthNetmWd7HohNdANTWuYtYqeRj4YXv2d_3gsUlmWPDxn_z0uKrRMsI3PiHje3wU7VXd8S-yrfRpNmAsE2O5aMdiTRzRlgA6HOlQM31udxKEQ80WfdqeONlpcR9MggW2MuWqbssmzmyL6R0YJizsNYUwQa5xxNcljy-WY5Zh5XfH_EQefCCic4Q" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nSuZMPYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh3.googleusercontent.com/IAqYthNetmWd7HohNdANTWuYtYqeRj4YXv2d_3gsUlmWPDxn_z0uKrRMsI3PiHje3wU7VXd8S-yrfRpNmAsE2O5aMdiTRzRlgA6HOlQM31udxKEQ80WfdqeONlpcR9MggW2MuWqbssmzmyL6R0YJizsNYUwQa5xxNcljy-WY5Zh5XfH_EQefCCic4Q" alt="" width="686" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The variable &lt;strong&gt;have_trophy&lt;/strong&gt; is written as &lt;code&gt;session.have_trophy&lt;/code&gt; here because it is a global variable. I made most of the progress-tracking &lt;code&gt;Booleans&lt;/code&gt; global variables because changing their values was typically done in modals, and the modals, as separate Web Flows, are different &lt;a href="https://support.airkit.com/docs/variable-namespaces"&gt;Activity Groups&lt;/a&gt; from the Web Page that held my castle floor.)&lt;/p&gt;

&lt;p&gt;Once my PC had earned their trophy, in addition to opening a different modal when interacting with the Cat King, I also wanted to have a little trophy float next to the PC’s sprite as they moved about the castle floor. As you may recall, I’d been kicking this idea around for a while, and I was prepared for it. The image of my PC was already inside a Container, and I could easily add the image of my trophy to the same Container. After a bit of resizing and tinkering with the margins of the trophy image until I was satisfied with its placement in relation to my PC, I toggled to the &lt;strong&gt;Advanced&lt;/strong&gt; tab of the Inspector, and set the Is Visible field so that it was equal to &lt;code&gt;session.have_trophy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S7Nlcvcm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/xCcz1mlMPqLWUPBe2P_9bhWT_ifKzRvhYERe-BWBzpXtdSISK505KN8gfLCFGhBQ6suw6nJGp0olBm8Y3N7HOSA2DtGVJTPql3zFbQ8y2MC29tMQ15EwRY-C0qDKHWdYeWEs1vDDBu-Of0IREwHNpWeRVgLUCCroCfYQbXR3bDZdIa3Q7gamZBP1wg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S7Nlcvcm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh4.googleusercontent.com/xCcz1mlMPqLWUPBe2P_9bhWT_ifKzRvhYERe-BWBzpXtdSISK505KN8gfLCFGhBQ6suw6nJGp0olBm8Y3N7HOSA2DtGVJTPql3zFbQ8y2MC29tMQ15EwRY-C0qDKHWdYeWEs1vDDBu-Of0IREwHNpWeRVgLUCCroCfYQbXR3bDZdIa3Q7gamZBP1wg" alt="" width="780" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the trophy would only appear visibly next to the PC if &lt;code&gt;session.have_trophy&lt;/code&gt; was equal to &lt;code&gt;TRUE&lt;/code&gt;. And, because the movement animation had been applied to the Container both the PC and the trophy shared, I did not need to define any more animations to account for it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bmL9MaB---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/iIT3Za-8PG27vva1EeHDG5knhNiUS9CjFeaWmQErpQd_HGk6UjexnoAvVmdV9O8OX3h-LRw_OvnU-wpufI32yHgmFLtaEVoFprS6yKLP4Hx_DScVrsVLX_iKHyQWNBip6FjxwJZpbOE5GqFr4VXqOMgF9uzxeRGT89UHAkOtfMq4FExUbNl2BsmQDw" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bmL9MaB---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh6.googleusercontent.com/iIT3Za-8PG27vva1EeHDG5knhNiUS9CjFeaWmQErpQd_HGk6UjexnoAvVmdV9O8OX3h-LRw_OvnU-wpufI32yHgmFLtaEVoFprS6yKLP4Hx_DScVrsVLX_iKHyQWNBip6FjxwJZpbOE5GqFr4VXqOMgF9uzxeRGT89UHAkOtfMq4FExUbNl2BsmQDw" alt="" width="336" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Pictured: A fancy cat doing a fancy dance with their new Cat Trophy. By this point, I had changed the colors of the buttons for aesthetic purposes.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Insight: There’s a lot you can do when you track interactions with Booleans
&lt;/h2&gt;

&lt;p&gt;Once I started tracking whether or not my PC had earned their trophy with the simple &lt;code&gt;Boolean&lt;/code&gt; &lt;strong&gt;have_trophy&lt;/strong&gt;, I realized I could do two extremely powerful things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Interact differently with objects or other characters based on the variable’s value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Render images visible or invisible based on the variable’s value.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These two things applied no matter what variable I made or what interaction I used it to track. I used these principles to create new ways for the PC to interact with their environment. Soon, my PC could do all of the things real cats can do: destroy a plant, hide in a box, and cast spells. Some of the Airscript expressions grew lengthy, but the underlying logic was always the same.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;By this point, the 48-hour period I had given myself to build out this game was drawing to a close. I added a little more polish – an opening Web Page so that each player could name their PC something different, a Web Page that allowed players to crown their PC upon becoming Cat King – and then my work was done.&lt;/p&gt;

&lt;p&gt;Given that Airkit isn’t a video game creation platform, I was quite happy with the end result. The tools Airkit provides might be structured with particular use cases in mind, but they’re also versatile enough that I was able to work around any out-of-the-box functionality that didn’t fit my use case, and I made much more progress in this 48-hour period using Airkit than I would have if I had tried to construct the same game from scratch.&lt;/p&gt;

&lt;p&gt;While the gameplay was relatively short and simple, in a way, this worked to my advantage: after my 48-hour flurry of artistic verve, I was excited to show of my creation, and the fact that a single playthrough didn’t require much time commitment meant that it was easy to coax everyone into giving it a try.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0osD2_Dh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/8yt5Kv8ykL8iBzPZLOnM-hzLsRN3yj8lREPWGEndbE0AdQqiO621PoEamQIRZsYqHWt9p4J5Tx-aTYzrbvrjwPuwNap7_BntaTrCedIedlFT9fAtoG2bZzCnFQlImT25Vj719fRXCg76BxeyWiKg2H45m213jfBEFlsuUbZVxqnpQDncFpcMKCX-2A" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0osD2_Dh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://lh5.googleusercontent.com/8yt5Kv8ykL8iBzPZLOnM-hzLsRN3yj8lREPWGEndbE0AdQqiO621PoEamQIRZsYqHWt9p4J5Tx-aTYzrbvrjwPuwNap7_BntaTrCedIedlFT9fAtoG2bZzCnFQlImT25Vj719fRXCg76BxeyWiKg2H45m213jfBEFlsuUbZVxqnpQDncFpcMKCX-2A" alt="" width="880" height="659"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Pictured: My cat, after having been forced to sit through the dozenth playthrough of Cat Trophy Kingdom. He just became Cat King again!)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>lowcode</category>
      <category>programming</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>30 Minute Builds: Google Lens Rebuild with Airkit</title>
      <dc:creator>Brooks Naylor</dc:creator>
      <pubDate>Thu, 25 Aug 2022 20:14:00 +0000</pubDate>
      <link>https://dev.to/airkit/30-minute-builds-google-lens-rebuild-with-airkit-3e5c</link>
      <guid>https://dev.to/airkit/30-minute-builds-google-lens-rebuild-with-airkit-3e5c</guid>
      <description>&lt;p&gt;A standard mental exercise that all software engineers and web developers inevitably participate in is looking at a piece of software or an application, and thinking to themselves: “How would I make that?” It can come from either a stylistic interest, wanting to see what their take or spin on the product could be, but it could also come from a place of intellectual curiosity, wanting to walk through their own steps on developing a product they see in front of them.&lt;/p&gt;

&lt;p&gt;As a web developer, I have started to try my hand at this more and more with low code tools, and wanting to see just how much faster building an application with them can be in the hands of an engineer. So I decided to take a stab at recreating a specific feature of Google Lens, which is to create an application that takes in an image with text present in it in a foreign language, and to translate that for the user into english.&lt;/p&gt;

&lt;p&gt;Feel free to follow along with this tutorial, or, if you would prefer, follow along with the Youtube version of this build below!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/XyJxgXybpZo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Overview&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QrtHEUt2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8og3mu5k9ek9mr8wdgr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QrtHEUt2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8og3mu5k9ek9mr8wdgr.png" alt="Image description" width="609" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application itself is deceptively simple to create. The front end is a simple page with a file upload control, and a few variable dependent controls that would display the results of the returned data from Google.&lt;/p&gt;

&lt;p&gt;It requires an integration with a Google Cloud application, however, with both the Google Cloud Vision and the Google Cloud Translation APIs enabled. The Data Flows would then use the Google Vision OCR capabilities to detect text in an image, and would then send that text to the Google Translation API. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To build along with this tutorial, you will need a few things to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access to the Airkit Studio&lt;/li&gt;
&lt;li&gt;A Google Cloud Platform Account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Creating the Google Cloud Application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oNT-8qaC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1e90s4c87ietdd38xbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oNT-8qaC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x1e90s4c87ietdd38xbp.png" alt="Image description" width="609" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step in building out this application is to create the Google Cloud Project we will be integrating with. First, create the project in the organization that you have access to. Once it is created, you first need to build the OAuth Consent Screen. The app type is “External,” and from there you’ll need to fill out the application information you’ll be displaying to users. No need to fill out any test users or scopes for the moment, we’ll be finishing those portions later in the build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W8ynnZI8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jd1c4ffq3cr4b0gd6baa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W8ynnZI8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jd1c4ffq3cr4b0gd6baa.png" alt="Image description" width="609" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, type either “translation” or “vision” into the search bar at the top of the cloud console. You’ll then navigate to either the Cloud Translation API or the Cloud Vision API, depending on what you searched, and click the Enable button on both. Fair warning, Google Cloud Services may require you to input a credit card at this time. For the purposes of this build, we won’t be exceeding the basic tier version of these API endpoints, but feel free to watch the youtube video linked above to follow along if you would like to avoid storing your credit card.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BY31bWLZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4t4zqm01t2kdn4odike.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BY31bWLZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n4t4zqm01t2kdn4odike.png" alt="Image description" width="648" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once both API’s have been enabled, we’re navigate back to the console using the hamburger menu in the top left, and click on the API’s and Services section. From here, we are going to be creating the authorization credentials for our integration. Click the Credentials option on the left, and click “Create Credentials” at the top of this page, and choose “OAuth Client ID” from the list of options. The application type is going to be “Web Application.” This will populate the fields we need to fill out for our OAuth Integration with Airkit. &lt;/p&gt;

&lt;p&gt;The only required inputs here, other than naming your application at the top of the page, are the specific Airkit Authorized Redirect URI’s for this application. You’re going to add both “&lt;a href="https://app.airkit.com/internal/session-gateway/v1/oauth/callback%E2%80%9D"&gt;https://app.airkit.com/internal/session-gateway/v1/oauth/callback”&lt;/a&gt; and “&lt;a href="https://us.api.prod.airkit.com/internal/sessions/v1/auth/callback%E2%80%9D"&gt;https://us.api.prod.airkit.com/internal/sessions/v1/auth/callback”&lt;/a&gt; as redirect URI’s here, and then you’ll click create. This will generate a Client ID and a Client Secret for your application! Be sure to copy and paste them somewhere, because we will be using them in our custom Airkit integration. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating the Custom Integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Back in the Airkit console, It's time to actually create the custom integration we're going to be using to connect to our Google application. In the integrations section of the console, create a new Custom Integration, and create a name and custom key for the integration. For authentication type, select OAuth 2.0. Then add two Integration Parameters, “client_id” and “client_secret”. Beneath those, select Authorization Code under the grant type. The token verb will be POST, and the token endpoint will be “&lt;a href="https://oauth2.googleapis.com/token%E2%80%9D"&gt;https://oauth2.googleapis.com/token”&lt;/a&gt;. For Authorization Endpoint, put “&lt;a href="https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&amp;amp;prompt=consent%E2%80%9D"&gt;https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&amp;amp;prompt=consent”&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--loCpSGJl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5i12t5ioqz2eqnepuoo9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--loCpSGJl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5i12t5ioqz2eqnepuoo9.png" alt="Image description" width="427" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now under scopes, we need to put two different scope URLs here, since we are accessing two differently scoped APIs within the same Google application, so separating them with a single space, put “&lt;a href="https://www.googleapis.com/auth/cloud-vision"&gt;https://www.googleapis.com/auth/cloud-vision&lt;/a&gt; &lt;a href="https://www.googleapis.com/auth/cloud-translation%E2%80%9D"&gt;https://www.googleapis.com/auth/cloud-translation”&lt;/a&gt;. The next two fields should be “Client Id” and “Client Secret”, and in these fields you will paste in those values you received from Google Cloud. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JiGUa4bT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ompix8omt3c9vlil7ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JiGUa4bT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ompix8omt3c9vlil7ee.png" alt="Image description" width="427" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, set up your Auth Token parameters using the example below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cczuM7GT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7k4vfqcf0fpabde1noob.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cczuM7GT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7k4vfqcf0fpabde1noob.png" alt="Image description" width="427" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Create, and our custom integration is ready for use! We’ll next establish a connection with our Google application.  In the Airkit Console, under Connected Accounts, click Create New and select the integration we just made after naming the account. The integration parameters we added should now appear here as fields to fill out. These will also be the Client ID and Client Secret that were generated for us by Google. Once you click create, the OAuth Consent screen you made in the cloud console should appear. Accept the request, and the integration is ready for use!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building the Airkit Application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a blank application and go to the Airkit Studio. Nn Settings, ddd the Google Integration we created in the Integrations section of the Builder, and select the Connected Account in the available dropdown. This allows our Airkit application to now interact with the Box application we created.&lt;/p&gt;

&lt;p&gt;Next, add a Visit a Link Trigger in the Journey Builder, and in the first Step of the Journey add a blank Web Flow. From there, go to the web flows builder, and add a Title and some instructions for the user using Label controls. Then, add a File Upload Control, and inside the control add a Button Control. Underneath this, add a Media Control, and below that two different label controls. Change the top label control to say “Translated Text.” &lt;/p&gt;

&lt;p&gt;In the Variable Tree, create two Activity level variables. One will be an asset variable called “uploaded_image,” and the other will be a text variable called “result.” Change the value of the URL in the Media Control to “activity.uploaded_image.downloadUrl,” and change the value of the last Label to “activity.result.” Your application should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sn-S6_sU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odw5am14478h1xl6tfej.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sn-S6_sU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odw5am14478h1xl6tfej.png" alt="Image description" width="658" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functionality through Data Flows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The last step is to build the Data Flows we will be using to hit the two different Google API endpoints. Create two different blank Data Flows in the Connections Builder. In the first one, which will be the Data Flow that uses the Google Vision endpoint, add an Asset Input called “uploaded_image.” Add an HTTP Request Data Operation, and under service make sure the Google Integration you’ve added has been selected. The method for this operation is POST, and the URL is "&lt;a href="https://vision.googleapis.com/v1/images:annotate"&gt;https://vision.googleapis.com/v1/images:annotate&lt;/a&gt;", according to the Google API documentation. Under Body, follow the example below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7KaOWVhD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shna3zvr4qqbaob7lzav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7KaOWVhD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shna3zvr4qqbaob7lzav.png" alt="Image description" width="678" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a new Transform Data Operation underneath this one, and in the expression follow this example below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--54a8BvVY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qb2x329z7kih6233cqiy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--54a8BvVY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qb2x329z7kih6233cqiy.png" alt="Image description" width="678" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will return a List, or an Array, of all the words found in the image we sent to the Google Vision API. The last step is to add the Run Data Flow Data Operation, and here is where we will be running the second Data Flow we created, which will hit the Google Translate API. Pass in the transform result, which should be a list of text. &lt;/p&gt;

&lt;p&gt;In the Translate Data Flow, let’s add an input called “words” that is a List of Text (feel free to paste the transform result from the previous page so we have something to work with here). Next, add a Transform Data Operation, and in the expression write JOIN(words, “ “). This will give us a single string of text to send to the Translate API. Next, add an HTTP Request Data Operation, which will look very similar to the operation from the previous Data Flow, except the URL will be "&lt;a href="https://translation.googleapis.com/language/translate/v2"&gt;https://translation.googleapis.com/language/translate/v2&lt;/a&gt;". For the body, follow the example below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KGX6JA90--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntd43uv96usjvkaf0yl8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KGX6JA90--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntd43uv96usjvkaf0yl8.png" alt="Image description" width="678" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, add another Transform operation, following this example to extract the results we need from the Translate API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---GJXMACI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p46o18gqxtexfy7lgqz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---GJXMACI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p46o18gqxtexfy7lgqz0.png" alt="Image description" width="678" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, make this most recent transform the return value at the bottom of this second Data Flow. Back on the first Data Flow, make the return value here the connectionOutput variable, which will contain the results of the second Data Flow. &lt;/p&gt;

&lt;p&gt;To wrap it all up, return to the Web Flows Builder and select the File Upload control on the left. Click the Actions tab in the inspector, and on the On Upload Finished Event, add first a Set Variable Action to assign a value to the variable bound to the Media Control URL value, and add a Run Data Flow Action to run the Google Vision Data Flow, and make the output binding the “activity.result” variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KQNrVZlW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1g0246dbc12xho23793h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KQNrVZlW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1g0246dbc12xho23793h.png" alt="Image description" width="678" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrap Up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once building out the application, a few thoughts initially came to mind. The first was to stress test the application as is without much tweaking to see how well this first iteration performs. The answer ended up being: fine? I went around my house with my phone with the app on screen and tested its ability to translate ingredients in my pantry. At its base level, the OCR we’re using still captures special characters, and has a hard time organizing text that isn’t laid out in an organized way, so the full text string it detects can be a bit disorganized depending on the image it analyzes. In the next iteration, I’m going to dive more into the Cloud Vision API documentation, and see if there are preventative measures against this, and I’m going to tweak the conditional logic in the Transformations I have in my Data Flows, to see if I can prevent this from happening.&lt;/p&gt;

&lt;p&gt;The second thought was just the vision of potential here. I instantly thought about new layouts for the front end, adding selectors for language result options, and better ways to more effectively lay out the information being taken in and being spit out by the application. I can’t wait to keep tinkering with this app and more like it!&lt;/p&gt;

</description>
      <category>lowcode</category>
      <category>googlecloud</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
