feat: pipedrive crm app on cal (#12316)
* add pipedrive crm app w/ revert api * update lockfile * fix issues highlighted by codacy * get pipedrive `client_id` & `client_secret` from db * update readme with instructions to add credentials * Fix yarn.lock * fix `turbo.json` --------- Co-authored-by: Hariom <hariombalhara@gmail.com>
This commit is contained in:
parent
5de77e386c
commit
1f036bf35e
|
@ -127,4 +127,12 @@ ZOHOCRM_CLIENT_ID=""
|
|||
ZOHOCRM_CLIENT_SECRET=""
|
||||
|
||||
|
||||
# - REVERT
|
||||
# Used for the Pipedrive integration (via/ Revert (https://revert.dev))
|
||||
# @see https://github.com/calcom/cal.com/#obtaining-revert-api-keys
|
||||
REVERT_API_KEY=
|
||||
REVERT_PUBLIC_TOKEN=
|
||||
|
||||
# NOTE: If you're self hosting Revert, update this URL to point to your own instance.
|
||||
REVERT_API_URL=https://api.revert.dev/
|
||||
# *********************************************************************************************************
|
||||
|
|
|
@ -554,6 +554,10 @@ following
|
|||
|
||||
[Follow these steps](./packages/app-store/zoho-bigin/)
|
||||
|
||||
### Obtaining Pipedrive Client ID and Secret
|
||||
|
||||
[Follow these steps](./packages/app-store/pipedrive-crm/)
|
||||
|
||||
## Workflows
|
||||
|
||||
### Setting up SendGrid for Email reminders
|
||||
|
|
|
@ -20,6 +20,7 @@ import { appKeysSchema as metapixel_zod_ts } from "./metapixel/zod";
|
|||
import { appKeysSchema as office365calendar_zod_ts } from "./office365calendar/zod";
|
||||
import { appKeysSchema as office365video_zod_ts } from "./office365video/zod";
|
||||
import { appKeysSchema as paypal_zod_ts } from "./paypal/zod";
|
||||
import { appKeysSchema as pipedrive_crm_zod_ts } from "./pipedrive-crm/zod";
|
||||
import { appKeysSchema as plausible_zod_ts } from "./plausible/zod";
|
||||
import { appKeysSchema as qr_code_zod_ts } from "./qr_code/zod";
|
||||
import { appKeysSchema as routing_forms_zod_ts } from "./routing-forms/zod";
|
||||
|
@ -57,6 +58,7 @@ export const appKeysSchemas = {
|
|||
office365calendar: office365calendar_zod_ts,
|
||||
office365video: office365video_zod_ts,
|
||||
paypal: paypal_zod_ts,
|
||||
"pipedrive-crm": pipedrive_crm_zod_ts,
|
||||
plausible: plausible_zod_ts,
|
||||
qr_code: qr_code_zod_ts,
|
||||
"routing-forms": routing_forms_zod_ts,
|
||||
|
|
|
@ -42,6 +42,7 @@ import office365video_config_json from "./office365video/config.json";
|
|||
import paypal_config_json from "./paypal/config.json";
|
||||
import ping_config_json from "./ping/config.json";
|
||||
import pipedream_config_json from "./pipedream/config.json";
|
||||
import pipedrive_crm_config_json from "./pipedrive-crm/config.json";
|
||||
import plausible_config_json from "./plausible/config.json";
|
||||
import qr_code_config_json from "./qr_code/config.json";
|
||||
import raycast_config_json from "./raycast/config.json";
|
||||
|
@ -120,6 +121,7 @@ export const appStoreMetadata = {
|
|||
paypal: paypal_config_json,
|
||||
ping: ping_config_json,
|
||||
pipedream: pipedream_config_json,
|
||||
"pipedrive-crm": pipedrive_crm_config_json,
|
||||
plausible: plausible_config_json,
|
||||
qr_code: qr_code_config_json,
|
||||
raycast: raycast_config_json,
|
||||
|
|
|
@ -20,6 +20,7 @@ import { appDataSchema as metapixel_zod_ts } from "./metapixel/zod";
|
|||
import { appDataSchema as office365calendar_zod_ts } from "./office365calendar/zod";
|
||||
import { appDataSchema as office365video_zod_ts } from "./office365video/zod";
|
||||
import { appDataSchema as paypal_zod_ts } from "./paypal/zod";
|
||||
import { appDataSchema as pipedrive_crm_zod_ts } from "./pipedrive-crm/zod";
|
||||
import { appDataSchema as plausible_zod_ts } from "./plausible/zod";
|
||||
import { appDataSchema as qr_code_zod_ts } from "./qr_code/zod";
|
||||
import { appDataSchema as routing_forms_zod_ts } from "./routing-forms/zod";
|
||||
|
@ -57,6 +58,7 @@ export const appDataSchemas = {
|
|||
office365calendar: office365calendar_zod_ts,
|
||||
office365video: office365video_zod_ts,
|
||||
paypal: paypal_zod_ts,
|
||||
"pipedrive-crm": pipedrive_crm_zod_ts,
|
||||
plausible: plausible_zod_ts,
|
||||
qr_code: qr_code_zod_ts,
|
||||
"routing-forms": routing_forms_zod_ts,
|
||||
|
|
|
@ -42,6 +42,7 @@ export const apiHandlers = {
|
|||
paypal: import("./paypal/api"),
|
||||
ping: import("./ping/api"),
|
||||
pipedream: import("./pipedream/api"),
|
||||
"pipedrive-crm": import("./pipedrive-crm/api"),
|
||||
plausible: import("./plausible/api"),
|
||||
qr_code: import("./qr_code/api"),
|
||||
raycast: import("./raycast/api"),
|
||||
|
|
|
@ -16,6 +16,7 @@ const appStore = {
|
|||
office365video: () => import("./office365video"),
|
||||
plausible: () => import("./plausible"),
|
||||
paypal: () => import("./paypal"),
|
||||
"pipedrive-crm": () => import("./pipedrive-crm"),
|
||||
salesforce: () => import("./salesforce"),
|
||||
zohocrm: () => import("./zohocrm"),
|
||||
sendgrid: () => import("./sendgrid"),
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
items:
|
||||
- pipedrive-banner.jpeg
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
|
@ -0,0 +1,19 @@
|
|||
## Pipedrive Integration via Revert
|
||||
|
||||
#### Obtaining Pipedrive Client ID and Secret
|
||||
|
||||
* Open [Pipedrive Developers Corner](https://developers.pipedrive.com/) and sign in to your account, or create a new one
|
||||
* Go to Settings > (company name) Developer Hub
|
||||
* Create a Pipedrive app, using the steps mentioned [here](https://pipedrive.readme.io/docs/marketplace-creating-a-proper-app#create-an-app-in-5-simple-steps)
|
||||
* You can skip this step and use the default revert Pipedrive app
|
||||
* Set `https://app.revert.dev/oauth-callback/pipedrive` as a callback url for your app
|
||||
* **Get your client\_id and client\_secret**:
|
||||
* Go to the "OAuth & access scopes" tab of your app
|
||||
* Copy your client\_id and client\_secret
|
||||
|
||||
#### Obtaining Revert API keys
|
||||
|
||||
* Create an account on Revert if you don't already have one. (https://app.revert.dev/sign-up)
|
||||
* Login to your revert dashboard (https://app.revert.dev/sign-in) and click on `Customize your apps` - `Pipedrive`
|
||||
* Enter the `client_id` and `client_secret` you copied in the previous step
|
||||
* Enter the `client_id` and `client_secret` previously copied to `Settings > Admin > Apps > CRM > Pipedrive` by clicking the `Edit` button on the app settings.
|
|
@ -0,0 +1,35 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { createDefaultInstallation } from "@calcom/app-store/_utils/installation";
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import appConfig from "../config.json";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" });
|
||||
const appKeys = await getAppKeysFromSlug(appConfig.slug);
|
||||
let client_id = "";
|
||||
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
|
||||
if (!client_id) return res.status(400).json({ message: "pipedrive client id missing." });
|
||||
// Check that user is authenticated
|
||||
req.session = await getServerSession({ req, res });
|
||||
const { teamId } = req.query;
|
||||
const userId = req.session?.user.id;
|
||||
if (!userId) {
|
||||
throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
|
||||
}
|
||||
await createDefaultInstallation({
|
||||
appType: `${appConfig.slug}_other_calendar`,
|
||||
userId: userId,
|
||||
slug: appConfig.slug,
|
||||
key: {},
|
||||
teamId: Number(teamId),
|
||||
});
|
||||
const tenantId = teamId ? teamId : userId;
|
||||
res.status(200).json({
|
||||
url: `https://oauth.pipedrive.com/oauth/authorize?client_id=${appKeys.client_id}&redirect_uri=https://app.revert.dev/oauth-callback/pipedrive&state={%22tenantId%22:%22${tenantId}%22,%22revertPublicToken%22:%22${process.env.REVERT_PUBLIC_TOKEN}%22}`,
|
||||
newTab: true,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
|
||||
import appConfig from "../config.json";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!req.session?.user?.id) {
|
||||
return res.status(401).json({ message: "You must be logged in to do this" });
|
||||
}
|
||||
|
||||
const state = decodeOAuthState(req);
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ??
|
||||
getInstalledAppPath({ variant: appConfig.variant, slug: appConfig.slug })
|
||||
);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { default as add } from "./add";
|
||||
export { default as callback } from "./callback";
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Pipedrive CRM",
|
||||
"slug": "pipedrive-crm",
|
||||
"type": "pipedrive-crm_other_calendar",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://revert.dev",
|
||||
"variant": "crm",
|
||||
"categories": ["crm"],
|
||||
"publisher": "Revert.dev ",
|
||||
"email": "jatin@revert.dev",
|
||||
"description": "Founded in 2010, Pipedrive is an easy and effective sales CRM that drives small business growth.\r\rToday, Pipedrive is used by revenue teams at more than 100,000 companies worldwide. Pipedrive is headquartered in New York and has offices across Europe and the US.\r\rThe company is backed by majority holder Vista Equity Partners, Bessemer Venture Partners, Insight Partners, Atomico, and DTCP.\r\rLearn more at www.pipedrive.com.",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "basic",
|
||||
"dirName": "pipedrive-crm"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
|
@ -0,0 +1,313 @@
|
|||
import { getLocation } from "@calcom/lib/CalEventParser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type {
|
||||
Calendar,
|
||||
CalendarEvent,
|
||||
EventBusyDate,
|
||||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import type { CredentialPayload } from "@calcom/types/Credential";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
type ContactSearchResult = {
|
||||
status: string;
|
||||
results: Array<{
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
name: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
type ContactCreateResult = {
|
||||
status: string;
|
||||
result: {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default class PipedriveCalendarService implements Calendar {
|
||||
private log: typeof logger;
|
||||
private tenantId: string;
|
||||
private revertApiKey: string;
|
||||
private revertApiUrl: string;
|
||||
constructor(credential: CredentialPayload) {
|
||||
this.revertApiKey = process.env.REVERT_API_KEY || "";
|
||||
this.revertApiUrl = process.env.REVERT_API_URL || "https://api.revert.dev/";
|
||||
this.tenantId = String(credential.teamId ? credential.teamId : credential.userId); // Question: Is this a reasonable assumption to be made? Get confirmation on the exact field to be used here.
|
||||
this.log = logger.getSubLogger({ prefix: [`[[lib] ${appConfig.slug}`] });
|
||||
}
|
||||
|
||||
private createContacts = async (attendees: Person[]) => {
|
||||
const result = attendees.map(async (attendee) => {
|
||||
const headers = new Headers();
|
||||
headers.append("x-revert-api-token", this.revertApiKey);
|
||||
headers.append("x-revert-t-id", this.tenantId);
|
||||
headers.append("Content-Type", "application/json");
|
||||
|
||||
const [firstname, lastname] = !!attendee.name ? attendee.name.split(" ") : [attendee.email, "-"];
|
||||
const bodyRaw = JSON.stringify({
|
||||
firstName: firstname,
|
||||
lastName: lastname || "-",
|
||||
email: attendee.email,
|
||||
});
|
||||
|
||||
const requestOptions = {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: bodyRaw,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.revertApiUrl}crm/contacts`, requestOptions);
|
||||
const result = (await response.json()) as ContactCreateResult;
|
||||
return result;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
return await Promise.all(result);
|
||||
};
|
||||
|
||||
private contactSearch = async (event: CalendarEvent) => {
|
||||
const result = event.attendees.map(async (attendee) => {
|
||||
const headers = new Headers();
|
||||
headers.append("x-revert-api-token", this.revertApiKey);
|
||||
headers.append("x-revert-t-id", this.tenantId);
|
||||
headers.append("Content-Type", "application/json");
|
||||
|
||||
const bodyRaw = JSON.stringify({ searchCriteria: attendee.email });
|
||||
|
||||
const requestOptions = {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: bodyRaw,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.revertApiUrl}crm/contacts/search`, requestOptions);
|
||||
const result = (await response.json()) as ContactSearchResult;
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { status: "error", results: [] };
|
||||
}
|
||||
});
|
||||
return await Promise.all(result);
|
||||
};
|
||||
|
||||
private getMeetingBody = (event: CalendarEvent): string => {
|
||||
return `<b>${event.organizer.language.translate("invitee_timezone")}:</b> ${
|
||||
event.attendees[0].timeZone
|
||||
}<br><br><b>${event.organizer.language.translate("share_additional_notes")}</b><br>${
|
||||
event.additionalNotes || "-"
|
||||
}`;
|
||||
};
|
||||
|
||||
private createPipedriveEvent = async (event: CalendarEvent, contacts: CalendarEvent["attendees"]) => {
|
||||
const eventPayload = {
|
||||
subject: event.title,
|
||||
startDateTime: event.startTime,
|
||||
endDateTime: event.endTime,
|
||||
description: this.getMeetingBody(event),
|
||||
location: getLocation(event),
|
||||
associations: {
|
||||
contactId: String(contacts[0].id),
|
||||
},
|
||||
};
|
||||
const headers = new Headers();
|
||||
headers.append("x-revert-api-token", this.revertApiKey);
|
||||
headers.append("x-revert-t-id", this.tenantId);
|
||||
headers.append("Content-Type", "application/json");
|
||||
|
||||
const eventBody = JSON.stringify(eventPayload);
|
||||
const requestOptions = {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: eventBody,
|
||||
};
|
||||
|
||||
return await fetch(`${this.revertApiUrl}crm/events`, requestOptions);
|
||||
};
|
||||
|
||||
private updateMeeting = async (uid: string, event: CalendarEvent) => {
|
||||
const eventPayload = {
|
||||
subject: event.title,
|
||||
startDateTime: event.startTime,
|
||||
endDateTime: event.endTime,
|
||||
description: this.getMeetingBody(event),
|
||||
location: getLocation(event),
|
||||
};
|
||||
const headers = new Headers();
|
||||
headers.append("x-revert-api-token", this.revertApiKey);
|
||||
headers.append("x-revert-t-id", this.tenantId);
|
||||
headers.append("Content-Type", "application/json");
|
||||
|
||||
const eventBody = JSON.stringify(eventPayload);
|
||||
const requestOptions = {
|
||||
method: "PATCH",
|
||||
headers: headers,
|
||||
body: eventBody,
|
||||
};
|
||||
|
||||
return await fetch(`${this.revertApiUrl}crm/events/${uid}`, requestOptions);
|
||||
};
|
||||
|
||||
private deleteMeeting = async (uid: string) => {
|
||||
const headers = new Headers();
|
||||
headers.append("x-revert-api-token", this.revertApiKey);
|
||||
headers.append("x-revert-t-id", this.tenantId);
|
||||
|
||||
const requestOptions = {
|
||||
method: "DELETE",
|
||||
headers: headers,
|
||||
};
|
||||
|
||||
return await fetch(`${this.revertApiUrl}crm/events/${uid}`, requestOptions);
|
||||
};
|
||||
|
||||
async handleEventCreation(event: CalendarEvent, contacts: CalendarEvent["attendees"]) {
|
||||
const meetingEvent = await (await this.createPipedriveEvent(event, contacts)).json();
|
||||
if (meetingEvent && meetingEvent.status === "ok") {
|
||||
this.log.debug("event:creation:ok", { meetingEvent });
|
||||
return Promise.resolve({
|
||||
uid: meetingEvent.result.id,
|
||||
id: meetingEvent.result.id,
|
||||
type: appConfig.slug,
|
||||
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 PipedriveCRM");
|
||||
}
|
||||
|
||||
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
|
||||
let contacts = await this.contactSearch(event);
|
||||
contacts = contacts.filter((c) => c.results.length >= 1);
|
||||
if (contacts && contacts.length) {
|
||||
if (contacts.length === event.attendees.length) {
|
||||
// all contacts are in Pipedrive CRM already.
|
||||
this.log.debug("contact:search:all", { event, contacts: contacts });
|
||||
const existingPeople = contacts.map((c) => {
|
||||
return {
|
||||
id: Number(c.results[0].id),
|
||||
name: `${c.results[0].firstName} ${c.results[0].lastName}`,
|
||||
email: c.results[0].email,
|
||||
timeZone: event.attendees[0].timeZone,
|
||||
language: event.attendees[0].language,
|
||||
};
|
||||
});
|
||||
return await this.handleEventCreation(event, existingPeople);
|
||||
} else {
|
||||
// Some attendees don't exist in PipedriveCRM
|
||||
// Get the existing contacts' email to filter out
|
||||
this.log.debug("contact:search:notAll", { event, contacts });
|
||||
const existingContacts = contacts.map((contact) => contact.results[0].email);
|
||||
this.log.debug("contact:filter:existing", { existingContacts });
|
||||
// Get non existing contacts filtering out existing from attendees
|
||||
const nonExistingContacts: Person[] = event.attendees.filter(
|
||||
(attendee) => !existingContacts.includes(attendee.email)
|
||||
);
|
||||
this.log.debug("contact:filter:nonExisting", { nonExistingContacts });
|
||||
// Only create contacts in PipedriveCRM that were not present in the previous contact search
|
||||
const createdContacts = await this.createContacts(nonExistingContacts);
|
||||
this.log.debug("contact:created", { createdContacts });
|
||||
// Continue with event creation and association only when all contacts are present in Pipedrive
|
||||
if (createdContacts[0] && createdContacts[0].status === "ok") {
|
||||
this.log.debug("contact:creation:ok");
|
||||
const existingPeople = contacts.map((c) => {
|
||||
return {
|
||||
id: Number(c.results[0].id),
|
||||
name: c.results[0].name,
|
||||
email: c.results[0].email,
|
||||
timeZone: nonExistingContacts[0].timeZone,
|
||||
language: nonExistingContacts[0].language,
|
||||
};
|
||||
});
|
||||
const newlyCreatedPeople = createdContacts.map((c) => {
|
||||
return {
|
||||
id: Number(c.result.id),
|
||||
name: c.result.name,
|
||||
email: c.result.email,
|
||||
timeZone: nonExistingContacts[0].timeZone,
|
||||
language: nonExistingContacts[0].language,
|
||||
};
|
||||
});
|
||||
const allContacts = existingPeople.concat(newlyCreatedPeople);
|
||||
// ensure the order of attendees is maintained.
|
||||
allContacts.sort((a, b) => {
|
||||
const indexA = event.attendees.findIndex((c) => c.email === a.email);
|
||||
const indexB = event.attendees.findIndex((c) => c.email === b.email);
|
||||
return indexA - indexB;
|
||||
});
|
||||
return await this.handleEventCreation(event, allContacts);
|
||||
}
|
||||
return Promise.reject({
|
||||
calError: "Something went wrong when creating non-existing attendees in PipedriveCRM",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.log.debug("contact:search:none", { event, contacts });
|
||||
const createdContacts = await this.createContacts(event.attendees);
|
||||
this.log.debug("contact:created", { createdContacts });
|
||||
if (createdContacts[0] && createdContacts[0].status === "ok") {
|
||||
this.log.debug("contact:creation:ok");
|
||||
const newContacts = createdContacts.map((c) => {
|
||||
return {
|
||||
id: Number(c.result.id),
|
||||
name: c.result.name,
|
||||
email: c.result.email,
|
||||
timeZone: event.attendees[0].timeZone,
|
||||
language: event.attendees[0].language,
|
||||
};
|
||||
});
|
||||
return await this.handleEventCreation(event, newContacts);
|
||||
}
|
||||
}
|
||||
return Promise.reject({
|
||||
calError: "Something went wrong when searching/creating the attendees in PipedriveCRM",
|
||||
});
|
||||
}
|
||||
|
||||
async updateEvent(uid: string, event: CalendarEvent): Promise<NewCalendarEventType> {
|
||||
const meetingEvent = await (await this.updateMeeting(uid, event)).json();
|
||||
if (meetingEvent && meetingEvent.status === "ok") {
|
||||
this.log.debug("event:updation:ok", { meetingEvent });
|
||||
return Promise.resolve({
|
||||
uid: meetingEvent.result.id,
|
||||
id: meetingEvent.result.id,
|
||||
type: appConfig.slug,
|
||||
password: "",
|
||||
url: "",
|
||||
additionalInfo: { meetingEvent },
|
||||
});
|
||||
}
|
||||
this.log.debug("meeting:updation:notOk", { meetingEvent, event });
|
||||
return Promise.reject("Something went wrong when updating a meeting in PipedriveCRM");
|
||||
}
|
||||
|
||||
async deleteEvent(uid: string): Promise<void> {
|
||||
await this.deleteMeeting(uid);
|
||||
}
|
||||
|
||||
async getAvailability(
|
||||
_dateFrom: string,
|
||||
_dateTo: string,
|
||||
_selectedCalendars: IntegrationCalendar[]
|
||||
): Promise<EventBusyDate[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async listCalendars(_event?: CalendarEvent): Promise<IntegrationCalendar[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as CalendarService } from "./CalendarService";
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/pipedrive-crm",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "Founded in 2010, Pipedrive is an easy and effective sales CRM that drives small business growth.\r\rToday, Pipedrive is used by revenue teams at more than 100,000 companies worldwide. Pipedrive is headquartered in New York and has offices across Europe and the US.\r\rThe company is backed by majority holder Vista Equity Partners, Bessemer Venture Partners, Insight Partners, Atomico, and DTCP.\r\rLearn more at www.pipedrive.com."
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<svg width="304px" height="304px" viewBox="0 0 304 304" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.4 (67378) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>
|
||||
Pipedrive_letter_logo_light@1,5x
|
||||
</title>
|
||||
<desc>
|
||||
Created with Sketch.
|
||||
</desc>
|
||||
<defs>
|
||||
<path d="M59.6807,81.1772 C59.6807,101.5343 70.0078,123.4949 92.7336,123.4949 C109.5872,123.4949 126.6277,110.3374 126.6277,80.8785 C126.6277,55.0508 113.232,37.7119 93.2944,37.7119 C77.0483,37.7119 59.6807,49.1244 59.6807,81.1772 Z M101.3006,0 C142.0482,0 169.4469,32.2728 169.4469,80.3126 C169.4469,127.5978 140.584,160.60942 99.3224,160.60942 C79.6495,160.60942 67.0483,152.1836 60.4595,146.0843 C60.5063,147.5305 60.5374,149.1497 60.5374,150.8788 L60.5374,215 L18.32565,215 L18.32565,44.157 C18.32565,41.6732 17.53126,40.8873 15.07021,40.8873 L0.5531,40.8873 L0.5531,3.4741 L35.9736,3.4741 C52.282,3.4741 56.4564,11.7741 57.2508,18.1721 C63.8708,10.7524 77.5935,0 101.3006,0 Z" id="path-1">
|
||||
</path>
|
||||
</defs>
|
||||
<g id="Pipedrive_letter_logo_light" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Pipedrive_monogram_logo_light" transform="translate(67.000000, 44.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use href="#path-1">
|
||||
</use>
|
||||
</mask>
|
||||
<use id="Clip-5" fill="#26292C" xlink:href="#path-1">
|
||||
</use>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
|
@ -0,0 +1,8 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const appKeysSchema = z.object({
|
||||
client_id: z.string().min(1),
|
||||
client_secret: z.string().min(1),
|
||||
});
|
||||
|
||||
export const appDataSchema = z.object({});
|
|
@ -353,6 +353,9 @@
|
|||
"ZOHOCRM_CLIENT_SECRET",
|
||||
"ZOOM_CLIENT_ID",
|
||||
"ZOOM_CLIENT_SECRET",
|
||||
"REVERT_API_KEY",
|
||||
"REVERT_API_URL",
|
||||
"REVERT_PUBLIC_TOKEN",
|
||||
"RESEND_API_KEY",
|
||||
"LOCAL_TESTING_DOMAIN_VERCEL",
|
||||
"AUTH_BEARER_TOKEN_CLOUDFLARE",
|
||||
|
|
Loading…
Reference in New Issue
Block a user