first commit
This commit is contained in:
6
calcom/packages/app-store/zoho-bigin/DESCRIPTION.md
Normal file
6
calcom/packages/app-store/zoho-bigin/DESCRIPTION.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
items:
|
||||
- 1.png
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
9
calcom/packages/app-store/zoho-bigin/README.md
Normal file
9
calcom/packages/app-store/zoho-bigin/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
### Obtaining Zoho Bigin Client ID and Secret
|
||||
|
||||
1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one.
|
||||
2. Click "ADD CLIENT" button top right and select "Server-based Applications".
|
||||
3. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/zoho-bigin/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||
4. Go to tab "Client Secret" tab.
|
||||
5. Now copy the Client ID and Client Secret to your .env.appStore file into the `ZOHO_BIGIN_CLIENT_ID` and `ZOHO_BIGIN_CLIENT_SECRET` fields.
|
||||
6. In the "Settings" section check the "Multi-DC" option if you wish to use the same OAuth credentials for all data centers.
|
||||
7. You're good to go. Now you can easily add Zoho Bigin from the Cal.com app store.
|
35
calcom/packages/app-store/zoho-bigin/api/add.ts
Normal file
35
calcom/packages/app-store/zoho-bigin/api/add.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import axios from "axios";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
|
||||
import appConfig from "../config.json";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "GET") {
|
||||
const appKeys = await getAppKeysFromSlug(appConfig.slug);
|
||||
|
||||
const clientId = typeof appKeys.client_id === "string" ? appKeys.client_id : "";
|
||||
if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." });
|
||||
|
||||
const redirectUri = `${WEBAPP_URL}/api/integrations/zoho-bigin/callback`;
|
||||
|
||||
const authUrl = axios.getUri({
|
||||
url: "https://accounts.zoho.com/oauth/v2/auth",
|
||||
params: {
|
||||
scope: appConfig.scope,
|
||||
client_id: clientId,
|
||||
response_type: "code",
|
||||
redirect_uri: redirectUri,
|
||||
access_type: "offline",
|
||||
state: encodeOAuthState(req),
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({ url: authUrl });
|
||||
return;
|
||||
}
|
||||
res.status(400).json({ message: "Invalid request method." });
|
||||
}
|
86
calcom/packages/app-store/zoho-bigin/api/callback.ts
Normal file
86
calcom/packages/app-store/zoho-bigin/api/callback.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import axios from "axios";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import qs from "qs";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential";
|
||||
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
|
||||
import appConfig from "../config.json";
|
||||
|
||||
function isAuthorizedAccountsServerUrl(accountsServer: string) {
|
||||
// As per https://www.zoho.com/crm/developer/docs/api/v6/multi-dc.html#:~:text=US:%20https://accounts.zoho,https://accounts.zohocloud.ca&text=The%20%22location=us%22%20parameter,domain%20in%20all%20API%20endpoints.&text=You%20must%20make%20the%20authorization,.zoho.com.cn.
|
||||
const authorizedAccountServers = [
|
||||
"https://accounts.zoho.com",
|
||||
"https://accounts.zoho.eu",
|
||||
"https://accounts.zoho.in",
|
||||
"https://accounts.zoho.com.cn",
|
||||
"https://accounts.zoho.jp",
|
||||
"https://accounts.zohocloud.ca",
|
||||
"https://accounts.zoho.com.au",
|
||||
];
|
||||
return authorizedAccountServers.includes(accountsServer);
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { code, "accounts-server": accountsServer } = req.query;
|
||||
const state = decodeOAuthState(req);
|
||||
|
||||
if (code && typeof code !== "string") {
|
||||
res.status(400).json({ message: "`code` must be a string" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accountsServer || typeof accountsServer !== "string") {
|
||||
res.status(400).json({ message: "`accounts-server` is required and must be a string" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthorizedAccountsServerUrl(accountsServer)) {
|
||||
res.status(400).json({ message: "`accounts-server` is not authorized" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.session?.user?.id) {
|
||||
res.status(401).json({ message: "You must be logged in to do this" });
|
||||
return;
|
||||
}
|
||||
|
||||
const appKeys = await getAppKeysFromSlug(appConfig.slug);
|
||||
|
||||
const clientId = typeof appKeys.client_id === "string" ? appKeys.client_id : "";
|
||||
const clientSecret = typeof appKeys.client_secret === "string" ? appKeys.client_secret : "";
|
||||
|
||||
if (!clientId) return res.status(400).json({ message: "Zoho Bigin client_id missing." });
|
||||
if (!clientSecret) return res.status(400).json({ message: "Zoho Bigin client_secret missing." });
|
||||
|
||||
const accountsUrl = `${accountsServer}/oauth/v2/token`;
|
||||
const redirectUri = `${WEBAPP_URL}/api/integrations/${appConfig.slug}/callback`;
|
||||
|
||||
const formData = {
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code: code,
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: "authorization_code",
|
||||
};
|
||||
|
||||
const tokenInfo = await axios.post(accountsUrl, qs.stringify(formData), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||||
},
|
||||
});
|
||||
|
||||
tokenInfo.data.expiryDate = Math.round(Date.now() + tokenInfo.data.expires_in);
|
||||
tokenInfo.data.accountServer = accountsServer;
|
||||
|
||||
await createOAuthAppCredential({ appId: appConfig.slug, type: appConfig.type }, tokenInfo.data, req);
|
||||
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ??
|
||||
getInstalledAppPath({ variant: appConfig.variant, slug: appConfig.slug })
|
||||
);
|
||||
}
|
2
calcom/packages/app-store/zoho-bigin/api/index.ts
Normal file
2
calcom/packages/app-store/zoho-bigin/api/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as add } from "./add";
|
||||
export { default as callback } from "./callback";
|
@ -0,0 +1,27 @@
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import AppCard from "@calcom/app-store/_components/AppCard";
|
||||
import useIsAppEnabled from "@calcom/app-store/_utils/useIsAppEnabled";
|
||||
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({ app, eventType }) {
|
||||
const pathname = usePathname();
|
||||
|
||||
const { enabled, updateEnabled } = useIsAppEnabled(app);
|
||||
|
||||
return (
|
||||
<AppCard
|
||||
returnTo={`${WEBAPP_URL}${pathname}?tabName=apps`}
|
||||
app={app}
|
||||
teamId={eventType.team?.id || undefined}
|
||||
switchOnClick={(e) => {
|
||||
updateEnabled(e);
|
||||
}}
|
||||
switchChecked={enabled}
|
||||
hideAppCardOptions
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventTypeAppCard;
|
19
calcom/packages/app-store/zoho-bigin/config.json
Normal file
19
calcom/packages/app-store/zoho-bigin/config.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Zoho Bigin",
|
||||
"slug": "zoho-bigin",
|
||||
"type": "zoho-bigin_crm",
|
||||
"logo": "zohobigin.svg",
|
||||
"url": "https://github.com/ShaneMaglangit",
|
||||
"variant": "crm",
|
||||
"categories": ["crm"],
|
||||
"extendsFeature": "EventType",
|
||||
"publisher": "Shane Maglangit",
|
||||
"email": "help@cal.com",
|
||||
"description": "Bigin easily transforms your day-to-day customer processes into actionable pipelines. From qualifying leads to closing deals to managing important after-sales operations—Bigin connects your different teams to work together so that you can offer the best possible experience to your customers. Say goodbye to missing follow-ups, manual data entry, lack of team communication, and information silos.",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "basic",
|
||||
"scope": "ZohoBigin.modules.events.ALL,ZohoBigin.modules.contacts.ALL",
|
||||
"isOAuth": true
|
||||
}
|
2
calcom/packages/app-store/zoho-bigin/index.ts
Normal file
2
calcom/packages/app-store/zoho-bigin/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
305
calcom/packages/app-store/zoho-bigin/lib/CrmService.ts
Normal file
305
calcom/packages/app-store/zoho-bigin/lib/CrmService.ts
Normal file
@ -0,0 +1,305 @@
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
|
||||
import { getLocation } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type {
|
||||
CalendarEvent,
|
||||
EventBusyDate,
|
||||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
import type { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { Contact, ContactCreateInput, CRM } from "@calcom/types/CrmService";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import refreshOAuthTokens from "../../_utils/oauth/refreshOAuthTokens";
|
||||
import { appKeysSchema } from "../zod";
|
||||
|
||||
export type BiginToken = {
|
||||
scope: string;
|
||||
api_domain: string;
|
||||
expires_in: number;
|
||||
expiryDate: number;
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
accountServer: string;
|
||||
refresh_token: string;
|
||||
};
|
||||
|
||||
export type BiginContact = {
|
||||
id: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export default class BiginCrmService implements CRM {
|
||||
private readonly integrationName = "zoho-bigin";
|
||||
private readonly auth: { getToken: () => Promise<BiginToken> };
|
||||
private log: typeof logger;
|
||||
private eventsSlug = "/bigin/v1/Events";
|
||||
private contactsSlug = "/bigin/v1/Contacts";
|
||||
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.auth = this.biginAuth(credential);
|
||||
this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
}
|
||||
|
||||
/***
|
||||
* Authenticate calendar service with Zoho Bigin provided credentials.
|
||||
*/
|
||||
private biginAuth(credential: CredentialPayload) {
|
||||
const credentialKey = credential.key as unknown as BiginToken;
|
||||
const credentialId = credential.id;
|
||||
|
||||
const isTokenValid = (token: BiginToken) =>
|
||||
token.access_token && token.expiryDate && token.expiryDate > Date.now();
|
||||
|
||||
return {
|
||||
getToken: () =>
|
||||
isTokenValid(credentialKey)
|
||||
? Promise.resolve(credentialKey)
|
||||
: this.refreshAccessToken(credentialId, credentialKey),
|
||||
};
|
||||
}
|
||||
|
||||
/***
|
||||
* Fetches a new access token if stored token is expired.
|
||||
*/
|
||||
private async refreshAccessToken(credentialId: number, credentialKey: BiginToken) {
|
||||
this.log.debug("Refreshing token as it's invalid");
|
||||
const grantType = "refresh_token";
|
||||
const accountsUrl = `${credentialKey.accountServer}/oauth/v2/token`;
|
||||
|
||||
const appKeys = await getAppKeysFromSlug(this.integrationName);
|
||||
|
||||
const { client_id: clientId, client_secret: clientSecret } = appKeysSchema.parse(appKeys);
|
||||
|
||||
const formData = {
|
||||
grant_type: grantType,
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
refresh_token: credentialKey.refresh_token,
|
||||
};
|
||||
|
||||
const tokenInfo = await refreshOAuthTokens(
|
||||
async () =>
|
||||
await axios.post(accountsUrl, qs.stringify(formData), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||||
},
|
||||
}),
|
||||
"zoho-bigin",
|
||||
credentialId
|
||||
);
|
||||
|
||||
if (!tokenInfo.data.error) {
|
||||
// set expiry date as offset from current time.
|
||||
tokenInfo.data.expiryDate = Math.round(Date.now() + tokenInfo.data.expires_in);
|
||||
tokenInfo.data.accountServer = credentialKey.accountServer;
|
||||
tokenInfo.data.refresh_token = credentialKey.refresh_token;
|
||||
|
||||
await prisma.credential.update({
|
||||
where: {
|
||||
id: credentialId,
|
||||
},
|
||||
data: {
|
||||
key: tokenInfo.data as BiginToken,
|
||||
},
|
||||
});
|
||||
this.log.debug("Fetched token", tokenInfo.data.access_token);
|
||||
} else {
|
||||
this.log.error(tokenInfo.data);
|
||||
}
|
||||
|
||||
return tokenInfo.data as BiginToken;
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates Zoho Bigin Contact records for every attendee added in event bookings.
|
||||
* Returns the results of all contact creation operations.
|
||||
*/
|
||||
async createContacts(contactsToCreate: ContactCreateInput[]) {
|
||||
const token = await this.auth.getToken();
|
||||
const contacts = contactsToCreate.map((contact) => {
|
||||
const nameParts = contact.name.split(" ");
|
||||
const firstName = nameParts[0];
|
||||
const lastName = nameParts.length > 1 ? nameParts.slice(1).join(" ") : "-";
|
||||
return {
|
||||
First_Name: firstName,
|
||||
Last_Name: lastName,
|
||||
Email: contact.email,
|
||||
};
|
||||
});
|
||||
|
||||
const response = await axios({
|
||||
method: "post",
|
||||
url: token.api_domain + this.contactsSlug,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Zoho-oauthtoken ${token.access_token}`,
|
||||
},
|
||||
data: JSON.stringify({ data: contacts }),
|
||||
});
|
||||
|
||||
return response
|
||||
? response.data.map((contact: BiginContact) => {
|
||||
return {
|
||||
id: contact.id,
|
||||
email: contact.email,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
}
|
||||
|
||||
/***
|
||||
* Finds existing Zoho Bigin Contact record based on email address. Returns a list of contacts objects that matched.
|
||||
*/
|
||||
async getContacts(emails: string | string[]) {
|
||||
const token = await this.auth.getToken();
|
||||
const emailsArray = Array.isArray(emails) ? emails : [emails];
|
||||
|
||||
const searchCriteria = `(${emailsArray.map((email) => `(Email:equals:${encodeURI(email)})`).join("or")})`;
|
||||
|
||||
const response = await axios({
|
||||
method: "get",
|
||||
url: `${token.api_domain}${this.contactsSlug}/search?criteria=${searchCriteria}`,
|
||||
headers: {
|
||||
authorization: `Zoho-oauthtoken ${token.access_token}`,
|
||||
},
|
||||
}).catch((e) => this.log.error("Error searching contact:", JSON.stringify(e), e.response?.data));
|
||||
|
||||
return response
|
||||
? response.data.map((contact: BiginContact) => {
|
||||
return {
|
||||
id: contact.id,
|
||||
email: contact.email,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
}
|
||||
|
||||
/***
|
||||
* Sends request to Zoho Bigin API to add new Events.
|
||||
*/
|
||||
private async createBiginEvent(event: CalendarEvent) {
|
||||
const token = await this.auth.getToken();
|
||||
const biginEvent = {
|
||||
Event_Title: event.title,
|
||||
Start_DateTime: toISO8601String(new Date(event.startTime)),
|
||||
End_DateTime: toISO8601String(new Date(event.endTime)),
|
||||
Description: event.additionalNotes,
|
||||
Location: getLocation(event),
|
||||
};
|
||||
|
||||
return axios({
|
||||
method: "post",
|
||||
url: token.api_domain + this.eventsSlug,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Zoho-oauthtoken ${token.access_token}`,
|
||||
},
|
||||
data: JSON.stringify({ data: [biginEvent] }),
|
||||
})
|
||||
.then((data) => data.data)
|
||||
.catch((e) => this.log.error("Error creating bigin event", JSON.stringify(e), e.response?.data));
|
||||
}
|
||||
|
||||
/***
|
||||
* Handles orchestrating the creation of new events in Zoho Bigin.
|
||||
*/
|
||||
async handleEventCreation(event: CalendarEvent, contacts: Contact[]) {
|
||||
const meetingEvent = await this.createBiginEvent(event);
|
||||
if (meetingEvent.data && meetingEvent.data.length && meetingEvent.data[0].status === "success") {
|
||||
this.log.debug("event:creation:ok", { meetingEvent });
|
||||
return Promise.resolve({
|
||||
uid: meetingEvent.data[0].details.id,
|
||||
id: meetingEvent.data[0].details.id,
|
||||
//FIXME: `externalCalendarId` is required by the `updateAllCalendarEvents` method, but is not used by zoho-bigin App. Not setting this property actually skips calling updateEvent..
|
||||
// Here the value doesn't matter. We just need to set it to something.
|
||||
externalCalendarId: "NO_CALENDAR_ID_NEEDED",
|
||||
type: this.integrationName,
|
||||
password: "",
|
||||
url: "",
|
||||
additionalInfo: { contacts, meetingEvent },
|
||||
});
|
||||
}
|
||||
this.log.debug("meeting:creation:notOk", { meetingEvent, event, contacts });
|
||||
return Promise.reject("Something went wrong when creating a meeting in Zoho Bigin");
|
||||
}
|
||||
|
||||
/***
|
||||
* Creates contacts and event records for new bookings.
|
||||
* Initially creates all new attendees as contacts, then creates the event.
|
||||
*/
|
||||
async createEvent(event: CalendarEvent, contacts: Contact[]): Promise<NewCalendarEventType> {
|
||||
return await this.handleEventCreation(event, contacts);
|
||||
}
|
||||
|
||||
/***
|
||||
* Updates an existing event in Zoho Bigin.
|
||||
*/
|
||||
async updateEvent(uid: string, event: CalendarEvent): Promise<NewCalendarEventType> {
|
||||
this.log.debug(`Updating Event with uid ${uid}`);
|
||||
const token = await this.auth.getToken();
|
||||
const biginEvent = {
|
||||
id: uid,
|
||||
Event_Title: event.title,
|
||||
Start_DateTime: toISO8601String(new Date(event.startTime)),
|
||||
End_DateTime: toISO8601String(new Date(event.endTime)),
|
||||
Description: event.additionalNotes,
|
||||
Location: getLocation(event),
|
||||
};
|
||||
|
||||
return axios
|
||||
.put(token.api_domain + this.eventsSlug, JSON.stringify({ data: [biginEvent] }), {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Zoho-oauthtoken ${token.access_token}`,
|
||||
},
|
||||
})
|
||||
.then((data) => data.data)
|
||||
.catch((e) => {
|
||||
this.log.error("Error in updating bigin event", JSON.stringify(e), e.response?.data);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEvent(uid: string): Promise<void> {
|
||||
const token = await this.auth.getToken();
|
||||
return axios
|
||||
.delete(`${token.api_domain}${this.eventsSlug}?ids=${uid}`, {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
authorization: `Zoho-oauthtoken ${token.access_token}`,
|
||||
},
|
||||
})
|
||||
.then((data) => data.data)
|
||||
.catch((e) => this.log.error("Error deleting bigin event", JSON.stringify(e), e.response?.data));
|
||||
}
|
||||
|
||||
async getAvailability(
|
||||
_dateFrom: string,
|
||||
_dateTo: string,
|
||||
_selectedCalendars: IntegrationCalendar[]
|
||||
): Promise<EventBusyDate[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async listCalendars(_event?: CalendarEvent): Promise<IntegrationCalendar[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
const toISO8601String = (date: Date) => {
|
||||
const tzo = -date.getTimezoneOffset(),
|
||||
dif = tzo >= 0 ? "+" : "-",
|
||||
pad = function (num: number) {
|
||||
return (num < 10 ? "0" : "") + num;
|
||||
};
|
||||
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(
|
||||
date.getHours()
|
||||
)}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${dif}${pad(Math.floor(Math.abs(tzo) / 60))}:${pad(
|
||||
Math.abs(tzo) % 60
|
||||
)}`;
|
||||
};
|
1
calcom/packages/app-store/zoho-bigin/lib/index.ts
Normal file
1
calcom/packages/app-store/zoho-bigin/lib/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as CrmService } from "./CrmService";
|
14
calcom/packages/app-store/zoho-bigin/package.json
Normal file
14
calcom/packages/app-store/zoho-bigin/package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/zoho-bigin",
|
||||
"version": "1.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "Bigin easily transforms your day-to-day customer processes into actionable pipelines. From qualifying leads to closing deals to managing important after-sales operations—Bigin connects your different teams to work together so that you can offer the best possible experience to your customers. Say goodbye to missing follow-ups, manual data entry, lack of team communication, and information silos."
|
||||
}
|
BIN
calcom/packages/app-store/zoho-bigin/static/1.png
Normal file
BIN
calcom/packages/app-store/zoho-bigin/static/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 KiB |
13
calcom/packages/app-store/zoho-bigin/static/zohobigin.svg
Normal file
13
calcom/packages/app-store/zoho-bigin/static/zohobigin.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="b" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" width="39.629" height="44" viewBox="0 0 39.629 44">
|
||||
<defs>
|
||||
<style>
|
||||
.d {
|
||||
fill: #039649;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="c" data-name="Layer 1">
|
||||
<path class="d" d="m19.21,44c-.66,0-1.32-.22-1.87-.55-.77-.55-1.32-1.54-1.32-2.53v-9.02L.4,4.73C-.15,3.63-.15,2.42.51,1.54c.55-.99,1.65-1.54,2.75-1.54h28.6c1.099,0,2.089.55,2.639,1.54.66.991.66,2.31.11,3.301l-1.87,3.3h3.74c1.1,0,2.089.55,2.639,1.54.66.99.66,2.31.11,3.3l-10.89,18.81v6.82c0,1.43-.88,2.53-2.2,2.97l-5.94,2.31c-.22.11-.66.11-.99.11M3.26,3.08q-.11.11-.11.221l15.51,27.059c.44.77.44,1.21.44,1.65v8.799s.11.11.22,0l5.94-2.31h.22v-6.6c0-.44,0-1.21.441-1.76l10.78-18.7c0-.11,0-.22-.11-.22l-19.69-.11,5.28,9.13,3.74-6.49c.44-.77,1.32-.99,2.09-.55.77.44.99,1.32.549,2.09l-4.399,7.59c-.991,1.54-3.19,1.43-4.071.11l-6.38-11.33c-.44-.66-.44-1.54,0-2.31.441-.77,1.211-1.32,2.09-1.32h13.42l2.75-4.73s0-.11-.11-.221H3.26Z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1021 B |
10
calcom/packages/app-store/zoho-bigin/zod.ts
Normal file
10
calcom/packages/app-store/zoho-bigin/zod.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { eventTypeAppCardZod } from "../eventTypeAppCardZod";
|
||||
|
||||
export const appKeysSchema = z.object({
|
||||
client_id: z.string().min(1),
|
||||
client_secret: z.string().min(1),
|
||||
});
|
||||
|
||||
export const appDataSchema = eventTypeAppCardZod;
|
Reference in New Issue
Block a user