Catalyst App within Zoho CRM by extension using Zoho Sigma Part 2

Our whole working application was ready and hosted on Catalyst in the last part. In this blog, we will create a plugin out of it with Zoho Sigma and install it in our CRM as a web view, adding authentication to it with permission management. Let’s get started without any delay. What is Zoho Sigma?...

Published on February 22, 2024

Share This Post
buildings stock resized

Our whole working application was ready and hosted on Catalyst in the last part. In this blog, we will create a plugin out of it with Zoho Sigma and install it in our CRM as a web view, adding authentication to it with permission management. Let’s get started without any delay.

What is Zoho Sigma?

Zoho Sigma serves as an extension development platform, empowering developers to create and host extensions for various Zoho applications. These extensions, or software add-ons, enhance the functionality of Zoho services. Developers can craft extensions using languages like HTML, JavaScript, and CSS. Sigma offers tools for managing server-side operations, enabling seamless integration with Zoho apps and third-party applications.

Creating a Zoho Sigma extension.

Before proceeding, make sure you have access to Sigma. This is how your dashboard will look like after logging in. Click on the New Extension Button on the top right.

sigma dashboard

Then add the information about the extension and select Zoho CRM as the service for which we create an extension.

zoho extension creation

Sigma allows you to create native as well as third-party extensions. For our purpose, our extension will be counted as a third party even though it is hosted on Catalyst which is part of Zoho. This is because if you are working on a real project and a development team is developing on Catalyst, the deployment pipeline would already be set. We can just provide a link to host as a web tab and the headache of updating the extension would go away. This keeps things isolated and automated.

Let us see how we can create a web tab which is a module of extension. You will find it inside the module page of the extension.

 

create web tab

We are creating the web tab as a widget and Sigma requires you to create a connected application first. Move to Connected Apps in the left menu bar and fill in the required information.

create connected app

You have to paste the URL where our Catalyst project is hosted. Finally, click on save, and now you can create a web tab widget as follows.

web app widget

With this, our widget is ready and now, let us install it in our CRM. This is fairly simple thanks to the simplicity of Sigma.

Voila!

extension published

Installing the plugin is just as straightforward as publishing it. We will copy the link and paste it into the browser. If you are logged in to the CRM, you will be prompted to the install extension screen automatically. This is how the window will look.

install extension

For now, install with default settings and we will show how to configure the permissions for the extension.

confirm install

And our extension is finally installed.

installed

To verify the installation of the extension, you will see it in the tab section in the upper right corner.

confirmation

Extension Authentication.

In the previous part, we had already added authentication but that was for our backend but for the frontend of extensions, we also need some authentication mechanisms. Since we are using CRM data in our extensions, authentication becomes necessary on the front end as well. Sigma provides us the option to add Auth0 to our extensions. In this section, we will see its implementation. This is what our connected app section looks like.

auth

In the last part, we had self client for our authentication. In this section, we will register our plugin’s front end as a client-side application from the Zoho API console.

auth

You can add the relevant details for your project and click on CREATE to get your client secret and client ID.

secrets

We will make some changes to our front-end code from the last blog.

import React, { useState, useEffect } from "react";


interface Policy {
 id: number;
 Name: string;
 dateOfIssue: string;
 dateOfExpiration: string;
 type: string;
 amount: number;
 annualPremium: number;
 state: string;
}
interface Contact {
 Full_Name: string;
 id: number;
}


const App = () => {
 const url = new URL(window.location.href);
 const scopeCode = url.searchParams.get("code") ?? "";
 const [token, setToken] = useState("");
 const [isAuthenticated, setIsAuthenticated] = useState(false);
 const [user, setUser] = useState({} as any);
 const [policies, setPolicies] = useState<Policy[]>([]);
 const [contacts, setContacts] = useState<Contact[]>([]);
 const [formData, setFormData] = useState<Policy>({
   Name: "",
   dateOfIssue: "",
   dateOfExpiration: "",
   type: "",
   amount: 0,
   annualPremium: 0,
   state: "",
   id: 0,
 });


 useEffect(() => {
   if (isAuthenticated) {
     fetchData();
   }
 }, [isAuthenticated]);


 useEffect(() => {
   const fetchToken = async () => {
     try {
       const tokenResponse = await fetch(
         "/server/policy_tracker_function/token",
         {
           method: "POST",
           headers: {
             "Content-Type": "application/json",
           },
           body: JSON.stringify({ code: scopeCode }),
         }
       );
       const tokenData = await tokenResponse.json();
       if (tokenData.token) {
         setToken(tokenData.token);
         const userResponse = await fetch(
           "/server/policy_tracker_function/user",
           {
             method: "GET",
             headers: {
               "Content-Type": "application/json",
               authorization: `${tokenData.token}`,
             },
           }
         );
         const userData = await userResponse.json();
         setUser(userData);
         setIsAuthenticated(true);
       }
     } catch (error) {
       console.error("Error fetching token:", error);
     }
   };
   if (scopeCode) {
     fetchToken();
   }
 }, [scopeCode]);
 const fetchData = async () => {
   try {
     const policiesResponse = await fetch(
       "/server/policy_tracker_function/policies",
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
         },
       }
     );
     const policiesData = await policiesResponse.json();
     setPolicies(policiesData);
     const contactsResponse = await fetch(
       "/server/policy_tracker_function/contacts",
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
           authorization: `${token}`,
         },
       }
     );
     const contactsData = await contactsResponse.json();
     setContacts(contactsData);
   } catch (error) {
     console.error("Error fetching data:", error);
   }
 };


 const handleChange = (e: any) => {
   setFormData({ ...formData, [e.target.name]: e.target.value });
 };


 const handleSubmit = async (e: any) => {
   e.preventDefault();


   try {
     await fetch("/server/policy_tracker_function/policies", {
       method: "POST",
       headers: {
         "Content-Type": "application/json",
       },
       body: JSON.stringify(formData),
     });
     setFormData({
       Name: "",
       dateOfIssue: "",
       dateOfExpiration: "",
       type: "",
       amount: 0,
       annualPremium: 0,
       state: "",
       id: 0,
     });
     fetchData();
   } catch (error) {
     console.error("Error adding policy:", error);
   }
 };
 const redirectUrl =
   "https://accounts.zoho.com/oauth/v2/auth?scope=ZohoCRM.users.READ,ZohoCRM.modules.contacts.READ&client_id=1000.OPCE1PD7P6UTQ124J96JFASMQLI68Q&response_type=code&access_type=online&redirect_uri=https://policytracker-841309168.development.catalystserverless.com/app/";


 return (
   <div
     style={{
       display: "flex",
       flexDirection: "column",
       alignItems: "center",
       justifyContent: "center",
       height: "100vh",
       width: "100vw",
     }}
   >
     <a
       href={redirectUrl}
       style={{
         display: isAuthenticated ? "none" : "block",
         backgroundColor: "blue",
         color: "white",
         padding: "10px",
         borderRadius: "10px",
         cursor: "pointer",
         textDecoration: "none",
       }}
     >
       Login With Zoho
     </a>


     {isAuthenticated && (
       <>
         <h1>Insurance Policies</h1>
         <div>
           {policies?.length === 0 && (
             <p>No policies found. Add a new policy</p>
           )}
           {policies?.map((policy) => (
             <div
               key={policy.id}
               style={{
                 border: "1px solid black",
                 padding: "10px",
                 margin: "10px",
                 borderRadius: "10px",
                 width: "fit-content",
               }}
             >
               <h3>{contacts?.find((c) => c.id === policy.id)?.Full_Name}</h3>
               <p>
                 <strong>Date of Issue:</strong> {policy.dateOfIssue}
               </p>
               <p>
                 <strong>Date of Expiration:</strong> {policy.dateOfExpiration}
               </p>
               <p>
                 <strong>Type:</strong> {policy.type}
               </p>
               <p>
                 <strong>Amount:</strong> {policy.amount} $
               </p>
               <p>
                 <strong>Annual Premium:</strong> {policy.annualPremium} $
               </p>
               <p>
                 <strong>State:</strong> {policy.state}
               </p>
               <br />
             </div>
           ))}
         </div>
         <h2>Add Policy</h2>
         <form onSubmit={handleSubmit}>
           <label>
             Name:
             <select name="id" onChange={handleChange} value={formData.id}>
               <option value=""></option>
               {contacts?.map((contact) => (
                 <option key={contact.id} value={contact.id}>
                   {contact.Full_Name}
                   {console.log(contact.Full_Name)}
                 </option>
               ))}
             </select>
           </label>
           <br />


           <br />
           <label>
             Date of Issue:
             <input
               type="date"
               name="dateOfIssue"
               onChange={handleChange}
               value={formData.dateOfIssue}
             />
           </label>
           <br />
           <label>
             Date of Expiration:
             <input
               type="date"
               name="dateOfExpiration"
               onChange={handleChange}
               value={formData.dateOfExpiration}
             />
           </label>
           <br />
           <label>
             Type:
             <select onChange={handleChange} name="type" id="type">
               <option value=""></option>


               <option value="Term">Term</option>
               <option value="Whole">Whole</option>
             </select>
           </label>
           <br />
           <label>
             Amount:
             <input
               type="number"
               name="amount"
               onChange={handleChange}
               value={formData.amount}
             />
           </label>
           <br />
           <label>
             Annual Premium:
             <input
               type="number"
               name="annualPremium"
               onChange={handleChange}
               value={formData.annualPremium}
             />
           </label>
           <br />
           <label>
             State:
             <select onChange={handleChange} name="state" id="state">
               <option value="CA">California</option>
               <option value="TX">Texas</option>
               <option value="FL">Florida</option>
               <option value="NY">New York</option>
               <option value="PA">Pennsylvania</option>
               <option value="IL">Illinois</option>
               <option value="OH">Ohio</option>
               <option value="GA">Georgia</option>
               <option value="NC">North Carolina</option>
               <option value="MI">Michigan</option>
             </select>
           </label>
           <br />
           <button type="submit">Add Policy</button>
         </form>
       </>
     )}
   </div>
 );
};


export default App;

Here, we have created a login button that will redirect you to the Zoho authentication page where you can log in with your Zoho credentials. After a successful login, you will be redirected to the redirect URL that was specified during client registration along with grant_code, which can be used to get your access token similarly.

The frontend will take this grant_code and request our serverless function for the token. We will also be fetching the user details from this token.

Let us create the respective endpoints in the backend for this purpose.

const express = require("express");
const axios = require("axios");


const app = express();
const PORT = process.env.PORT || 3000;


// Middleware for parsing JSON requests
app.use(express.json());
// Zoho CRM API configuration
const ZOHO_API_ENDPOINT =
 "https://www.zohoapis.com/crm/v6/Contacts?fields=Full_Name";


let policyData = [];


// Function to fetch contacts from Zoho CRM
const fetchContacts = async (token) => {
 try {
   const response = await axios.get(ZOHO_API_ENDPOINT, {
     headers: {
       Authorization: `Bearer ${token}`,
     },
   });
   console.log(response.data.data);
   return response.data.data;
 } catch (error) {
   throw error;
 }
};
app.post("/token", async (req, res) => {
 clg;
 const grantCode = req.body.code;
 const formData = new FormData();
 formData.append("grant_type", "authorization_code");
 formData.append("client_id", "your_client_id");
 formData.append(
   "client_secret",
   "your_client_secret"
 );
 formData.append(
   "redirect_uri",
   "https://policytracker-841309168.development.catalystserverless.com/app/"
 );
 formData.append("code", grantCode);


 await axios
   .post("https://accounts.zoho.com/oauth/v2/token", formData, {
     headers: {
       "Content-Type": "application/x-www-form-urlencoded",
     },
   })
   .then((response) => {
     console.log(response.data);
     res.json({ token: response.data.access_token });
   })
   .catch((error) => {
     console.error("Error fetching contacts from Zoho CRM:", error);
     res.status(500).send("Error fetching contacts from Zoho CRM");
   });
});
app.get("/currentUser", async (req, res) => {
 const response = await axios.get(
   ZOHO_API_ENDPOINT,
   {
     headers: {
       Authorization: `Bearer ${req.headers.authorization}`,
     },
   }
 );


 const userData = response.data.users[0];
 res.json(userData);
});
app.get("/policies", async (req, res) => {
 try {
   res.json(policyData);
 } catch (error) {
   res.status(500).send("Error fetching contacts from Zoho CRM");
 }
});


app.post("/policies", (req, res) => {
 const newPolicy = req.body;
 policyData.push(newPolicy);
 res.status(201).json(newPolicy);
});


app.get("/contacts", async (req, res) => {
 const authorizationToken = req.headers.authorization;
 try {
   const contacts = await fetchContacts(authorizationToken);
   res.json(contacts);
 } catch (error) {
   res.status(401).send("Error fetching contacts from Zoho CRM");
 }
});


// Start the server
app.listen(PORT, () => {
 console.log(`Server is running on http://localhost:${PORT}`);
});
module.exports = app;

Compared to the last blog, we are now getting the token from frontend to make requests to our Zoho CRM. We have introduced two new REST API endpoints.

/currentUser: Responsible for getting the current user who made the request.

/token: For generation of access token.

The next section is managing data level and view level permissions. We will test the whole flow all together!

Managing extension permissions.

View level permissions

As we have successfully created and installed our extension, we need to know more about the permissions. Extension visibility and functionality depend on the needs of the organization. Sometimes, an extension is required for every team while in other cases, some teams or users do not have to see or use the extension. This is where extension permissions come into play.

To test this properly, we have created two additional profiles in our CRM which can be seen below.

profiles

We also created a testing user that currently belongs to the Insurance Analysts profile.

test view

The view level permissions will work as follows.

working

For the profiles who are allowed to see the policy manager, only for them, the policy manager tab will be visible in the nav bar.

Let us head back to the extension settings, go to the change permission page, and permit the Insurance Analysts profile.

open permissions
allow

Now, if you access CRM from the test account, you will see the Policy Manager extension there as well.

In the test account

Profile: Insurance Analysts.

extension

Let us try removing permission or assigning this profile to another group and coming back to the CRM. Our test user belongs to the Email Team now.

changed

We will just refresh the page where our test user is logged in and see the results reflected!

results

The web tab is gone!

Data level permissions.

With our API, we can also implement data-level permissions as sometimes you do not want specific data visible to all users. For example, an admin can view everything but some teams will have restricted information access. We already have two types of user profiles: Administrator and Insurance analysts.

Let us define the permission rights for these profiles

Adminstrator: Has all viewing rights.

Insurance analysts: Cannot view Annual Premium or create Annual Premium

To make things clear, we have a diagram.

analysts

When the Administrator is logged in, he will be able to see both amounts for policies but our insurance analyst profiles will only be able to see one. To make this work, we will introduce some more changes to our code.

Frontend

const policiesResponse = await fetch(
       "/server/policy_tracker_function/policies",
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
           authorization: `${token}`,
           userProfile: user.profile.name,
         },
       }
     );


///previous code
         {user.profile.name !== "Insurance Analyst" ? (
             <label>
               Annual Premium:
               <input
                 type="number"
                 name="annualPremium"
                 onChange={handleChange}
                 value={formData.annualPremium}
               />
             </label>
           ) : (
             ""
           )}

Backend

app.get("/policies", async (req, res) => {
const userProfile = req.headers.userProfile;

try {
if (userProfile === “Insurance Analyst”) {
res.json(
policyData.map((policy) => ({ …policy, annualPremium: “*****” }))
);
return;
}

res.json(policyData);
} catch (error) {
res.status(500).send(“Error fetching contacts from Zoho CRM”);
}
});

We are sending the userProfile header which is simply the name of user’s profile we get from the CRM and in the backend, we are hiding the annualPremium information from the Insurance Analyst profile. Make sure you deploy everything with catalyst deploy

Testing everything.

Moments of truth, let us test our auth and data level permissions!

The account having the profile name as Insurance Analyst.

results

Now with the Administrator.

results

As you can see above, only the relevant data is visible to the respective user!

Conclusion

In this final blog, we discovered different aspects of extensions like authentication, installation of a Sigma plugin along permission management inside the CRM. We hope that this has made your Zoho concepts even stronger!

Recent Posts
  • Zoho CRM Next Gen UI: Using the Interactions Tab
  • How to Navigate the Next Gen UI of Zoho CRM?
  • Zoho CRM: Comprehensive Guide to Team Modules
  • The Ultimate Guide to Teamspaces in Zoho CRM for Everyone
Share This Post

Related Posts

Discover the latest news and updates on Zoho applications.

Unlock Your Knowledge Journey!

Get three articles for free, then enjoy unlimited access by registering.