HubSpot App (#2380)
* Initial changes * OAuth done and credentials stored * Added "other" integrations * Switching to hubspot api client * Event creation for all attendees * Update and delete done * Doc update * Fixing types * App label is not mandatory * Fixing bad merge: App label deleted * Fixing bad automerge * Removing c.log Co-authored-by: Omar López <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
2cafe2d98e
commit
ffebe8e901
23 changed files with 562 additions and 1925 deletions
|
@ -8,6 +8,7 @@
|
|||
# - APP STORE
|
||||
# - DAILY.CO VIDEO
|
||||
# - GOOGLE CALENDAR/MEET/LOGIN
|
||||
# - HUBSPOT
|
||||
# - OFFICE 365
|
||||
# - SLACK
|
||||
# - STRIPE
|
||||
|
@ -124,6 +125,12 @@ GOOGLE_API_CREDENTIALS='{}'
|
|||
# @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications
|
||||
GOOGLE_LOGIN_ENABLED=false
|
||||
|
||||
# - HUBSPOT
|
||||
# Used for the HubSpot integration
|
||||
# @see https://github.com/calcom/cal.com/#obtaining-hubspot-client-id-and-secret
|
||||
HUBSPOT_CLIENT_ID=""
|
||||
HUBSPOT_CLIENT_SECRET=""
|
||||
|
||||
# - OFFICE 365
|
||||
# Used for the Office 365 / Outlook.com Calendar / MS Teams integration
|
||||
# @see https://github.com/calcom/cal.com/#Obtaining-Microsoft-Graph-Client-ID-and-Secret
|
||||
|
@ -145,7 +152,7 @@ PAYMENT_FEE_FIXED=10 # Take 10 additional cents commission
|
|||
PAYMENT_FEE_PERCENTAGE=0.005 # Take 0.5% commission
|
||||
|
||||
# - TANDEM
|
||||
# Used for the Tandem integration -- contact support@tandem.chat to for API access.
|
||||
# Used for the Tandem integration -- contact support@tandem.chat for API access.
|
||||
TANDEM_CLIENT_ID=""
|
||||
TANDEM_CLIENT_SECRET=""
|
||||
TANDEM_BASE_URL="https://tandem.chat"
|
||||
|
|
13
README.md
13
README.md
|
@ -384,6 +384,19 @@ Next make sure you have your app running `yarn dx`. Then in the slack chat type
|
|||
4. Now paste the API key to your .env file into the `DAILY_API_KEY` field in your .env file.
|
||||
5. If you have the [Daily Scale Plan](https://www.daily.co/pricing) set the `DAILY_SCALE_PLAN` variable to `true` in order to use features like video recording.
|
||||
|
||||
### Obtaining HubSpot Client ID and Secret
|
||||
|
||||
1. Open [HubSpot Developer](https://developer.hubspot.com/) and sign into your account, or create a new one.
|
||||
2. From within the home of the Developer account page, go to "Manage apps".
|
||||
3. Click "Create app" button top right.
|
||||
4. Fill in any information you want in the "App info" tab
|
||||
5. Go to tab "Auth"
|
||||
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
|
||||
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot othercalendar/callback` replacing Cal.com URL with the URI at which your application runs.
|
||||
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
|
||||
9. Click the "Save" button at the bottom footer.
|
||||
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
|
||||
|
||||
<!-- LICENSE -->
|
||||
|
||||
## License
|
||||
|
|
|
@ -31,7 +31,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
throw new HttpError({ statusCode: 404, message: `API handler not found` });
|
||||
|
||||
const response = await handler(req, res);
|
||||
console.log("response", response);
|
||||
|
||||
return res.status(200);
|
||||
} catch (error) {
|
||||
|
|
|
@ -574,8 +574,8 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
// `flatMap()` these work like `.filter()` but infers the types correctly
|
||||
const conferencing = apps.flatMap((item) => (item.variant === "conferencing" ? [item] : []));
|
||||
const payment = apps.flatMap((item) => (item.variant === "payment" ? [item] : []));
|
||||
const other = apps.flatMap((item) => (item.variant.startsWith("other") ? [item] : []));
|
||||
const calendar = apps.flatMap((item) => (item.variant === "calendar" ? [item] : []));
|
||||
const other = apps.flatMap((item) => (item.variant === "other" ? [item] : []));
|
||||
return {
|
||||
conferencing: {
|
||||
items: conferencing,
|
||||
|
|
|
@ -12,7 +12,7 @@ export const getCalendar = (credential: Credential | null): Calendar | null => {
|
|||
const { type: calendarType } = credential;
|
||||
const calendarApp = appStore[calendarType.split("_").join("") as keyof typeof appStore];
|
||||
if (!(calendarApp && "lib" in calendarApp && "CalendarService" in calendarApp.lib)) {
|
||||
log.warn(`calendar of type ${calendarType} does not implemented`);
|
||||
log.warn(`calendar of type ${calendarType} is not implemented`);
|
||||
return null;
|
||||
}
|
||||
const CalendarService = calendarApp.lib.CalendarService;
|
||||
|
|
|
@ -5,7 +5,7 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
|||
import { App } from "@calcom/types/App";
|
||||
|
||||
function useAddAppMutation(type: App["type"], options?: Parameters<typeof useMutation>[2]) {
|
||||
const appName = type.replace("_", "");
|
||||
const appName = type.replaceAll("_", "");
|
||||
const mutation = useMutation(async () => {
|
||||
const state: IntegrationOAuthCallbackState = {
|
||||
returnTo: WEBAPP_URL + "/apps/installed" + location.search,
|
||||
|
|
|
@ -13,6 +13,7 @@ export const InstallAppButtonMap = {
|
|||
applecalendar: dynamic(() => import("./applecalendar/components/InstallAppButton")),
|
||||
caldavcalendar: dynamic(() => import("./caldavcalendar/components/InstallAppButton")),
|
||||
googlecalendar: dynamic(() => import("./googlecalendar/components/InstallAppButton")),
|
||||
hubspotothercalendar: dynamic(() => import("./hubspotothercalendar/components/InstallAppButton")),
|
||||
office365calendar: dynamic(() => import("./office365calendar/components/InstallAppButton")),
|
||||
slackmessaging: dynamic(() => import("./slackmessaging/components/InstallAppButton")),
|
||||
stripepayment: dynamic(() => import("./stripepayment/components/InstallAppButton")),
|
||||
|
@ -29,7 +30,7 @@ export const InstallAppButton = (
|
|||
) => {
|
||||
const { status } = useSession();
|
||||
const { t } = useLocale();
|
||||
const appName = props.type.replace("_", "") as keyof typeof InstallAppButtonMap;
|
||||
const appName = props.type.replaceAll("_", "") as keyof typeof InstallAppButtonMap;
|
||||
const InstallAppButtonComponent = InstallAppButtonMap[appName];
|
||||
if (!InstallAppButtonComponent) return null;
|
||||
if (status === "unauthenticated")
|
||||
|
|
8
packages/app-store/hubspotothercalendar/README.mdx
Normal file
8
packages/app-store/hubspotothercalendar/README.mdx
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
items:
|
||||
- /api/app-store/hubspotothercalendar/hubspot01.webp
|
||||
---
|
||||
|
||||
<Slider items={items} />
|
||||
|
||||
HubSpot is a cloud-based CRM designed to help align sales and marketing teams, foster sales enablement, boost ROI and optimize your inbound marketing strategy to generate more, qualified leads.
|
22
packages/app-store/hubspotothercalendar/api/add.ts
Normal file
22
packages/app-store/hubspotothercalendar/api/add.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import * as hubspot from "@hubspot/api-client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
|
||||
const scopes = ["crm.objects.contacts.read", "crm.objects.contacts.write"];
|
||||
|
||||
const client_id = process.env.HUBSPOT_CLIENT_ID;
|
||||
const hubspotClient = new hubspot.Client();
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!client_id) {
|
||||
res.status(400).json({ message: "HubSpot client id missing." });
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === "GET") {
|
||||
const redirectUri = WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback";
|
||||
const url = hubspotClient.oauth.getAuthorizationUrl(client_id, redirectUri, scopes.join(" "));
|
||||
res.status(200).json({ url });
|
||||
}
|
||||
}
|
56
packages/app-store/hubspotothercalendar/api/callback.ts
Normal file
56
packages/app-store/hubspotothercalendar/api/callback.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import * as hubspot from "@hubspot/api-client";
|
||||
import { TokenResponseIF } from "@hubspot/api-client/lib/codegen/oauth/models/TokenResponseIF";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { decodeOAuthState } from "../../_utils/decodeOAuthState";
|
||||
|
||||
const client_id = process.env.HUBSPOT_CLIENT_ID;
|
||||
const client_secret = process.env.HUBSPOT_CLIENT_SECRET;
|
||||
const hubspotClient = new hubspot.Client();
|
||||
|
||||
export type HubspotToken = TokenResponseIF & {
|
||||
expiryDate?: number;
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { code } = req.query;
|
||||
|
||||
if (code && typeof code !== "string") {
|
||||
res.status(400).json({ message: "`code` must be a string" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!client_id) {
|
||||
res.status(400).json({ message: "HubSpot client id missing." });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!client_secret) {
|
||||
res.status(400).json({ message: "HubSpot client secret missing." });
|
||||
return;
|
||||
}
|
||||
|
||||
const hubspotToken: HubspotToken = await hubspotClient.oauth.tokensApi.createToken(
|
||||
"authorization_code",
|
||||
code,
|
||||
WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback",
|
||||
client_id,
|
||||
client_secret
|
||||
);
|
||||
|
||||
// set expiry date as offset from current time.
|
||||
hubspotToken.expiryDate = Math.round(Date.now() + hubspotToken.expiresIn * 1000);
|
||||
await prisma.credential.create({
|
||||
data: {
|
||||
type: "hubspot_other_calendar",
|
||||
key: hubspotToken as any,
|
||||
userId: req.session?.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
const state = decodeOAuthState(req);
|
||||
res.redirect(state?.returnTo ?? "/apps/installed");
|
||||
}
|
2
packages/app-store/hubspotothercalendar/api/index.ts
Normal file
2
packages/app-store/hubspotothercalendar/api/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default as add } from "./add";
|
||||
export { default as callback } from "./callback";
|
|
@ -0,0 +1,18 @@
|
|||
import type { InstallAppButtonProps } from "@calcom/app-store/types";
|
||||
|
||||
import useAddAppMutation from "../../_utils/useAddAppMutation";
|
||||
|
||||
export default function InstallAppButton(props: InstallAppButtonProps) {
|
||||
const mutation = useAddAppMutation("hubspot_other_calendar");
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.render({
|
||||
onClick() {
|
||||
mutation.mutate("");
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { default as InstallAppButton } from "./InstallAppButton";
|
27
packages/app-store/hubspotothercalendar/index.ts
Normal file
27
packages/app-store/hubspotothercalendar/index.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import type { App } from "@calcom/types/App";
|
||||
|
||||
import _package from "./package.json";
|
||||
|
||||
export const metadata = {
|
||||
name: "HubSpot CRM",
|
||||
description: _package.description,
|
||||
installed: !!(process.env.HUBSPOT_CLIENT_ID && process.env.HUBSPOT_CLIENT_SECRET),
|
||||
type: "hubspot_other_calendar",
|
||||
imageSrc: "/api/app-store/hubspotothercalendar/icon.svg",
|
||||
variant: "other_calendar",
|
||||
logo: "/api/app-store/hubspotothercalendar/icon.svg",
|
||||
publisher: "Cal.com",
|
||||
url: "https://hubspot.com/",
|
||||
verified: true,
|
||||
rating: 4.3, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
reviews: 69, // TODO: placeholder for now, pull this from TrustPilot or G2
|
||||
category: "other",
|
||||
label: "HubSpot CRM",
|
||||
slug: "hubspot",
|
||||
title: "HubSpot CRM",
|
||||
trending: true,
|
||||
email: "help@cal.com",
|
||||
} as App;
|
||||
|
||||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
212
packages/app-store/hubspotothercalendar/lib/CalendarService.ts
Normal file
212
packages/app-store/hubspotothercalendar/lib/CalendarService.ts
Normal file
|
@ -0,0 +1,212 @@
|
|||
import * as hubspot from "@hubspot/api-client";
|
||||
import { BatchInputPublicAssociation } from "@hubspot/api-client/lib/codegen/crm/associations";
|
||||
import { PublicObjectSearchRequest } from "@hubspot/api-client/lib/codegen/crm/contacts";
|
||||
import { BatchInputSimplePublicObjectInput } from "@hubspot/api-client/lib/codegen/crm/objects";
|
||||
import { SimplePublicObjectInput } from "@hubspot/api-client/lib/codegen/crm/objects/meetings";
|
||||
import { Credential } from "@prisma/client";
|
||||
|
||||
import { getLocation, getAdditionalNotes } from "@calcom/lib/CalEventParser";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type {
|
||||
AdditionInformation,
|
||||
Calendar,
|
||||
CalendarEvent,
|
||||
ConferenceData,
|
||||
EventBusyDate,
|
||||
IntegrationCalendar,
|
||||
NewCalendarEventType,
|
||||
} from "@calcom/types/Calendar";
|
||||
|
||||
import type { HubspotToken } from "../api/callback";
|
||||
|
||||
const hubspotClient = new hubspot.Client();
|
||||
|
||||
const client_id = process.env.HUBSPOT_CLIENT_ID;
|
||||
const client_secret = process.env.HUBSPOT_CLIENT_SECRET;
|
||||
|
||||
export default class HubspotOtherCalendarService implements Calendar {
|
||||
private url = "";
|
||||
private integrationName = "";
|
||||
private auth: { getToken: () => Promise<any> };
|
||||
private log: typeof logger;
|
||||
|
||||
constructor(credential: Credential) {
|
||||
this.integrationName = "hubspot_other_calendar";
|
||||
|
||||
this.auth = this.hubspotAuth(credential);
|
||||
|
||||
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
|
||||
}
|
||||
|
||||
private hubspotContactSearch = async (event: CalendarEvent) => {
|
||||
const publicObjectSearchRequest: PublicObjectSearchRequest = {
|
||||
filterGroups: event.attendees.map((attendee) => ({
|
||||
filters: [
|
||||
{
|
||||
value: attendee.email,
|
||||
propertyName: "email",
|
||||
operator: "EQ",
|
||||
},
|
||||
],
|
||||
})),
|
||||
sorts: ["hs_object_id"],
|
||||
properties: ["hs_object_id", "email"],
|
||||
limit: 10,
|
||||
after: 0,
|
||||
};
|
||||
|
||||
return await hubspotClient.crm.contacts.searchApi
|
||||
.doSearch(publicObjectSearchRequest)
|
||||
.then((apiResponse) => apiResponse.results);
|
||||
};
|
||||
|
||||
private getHubspotMeetingBody = (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 hubspotCreateMeeting = async (event: CalendarEvent) => {
|
||||
const simplePublicObjectInput: SimplePublicObjectInput = {
|
||||
properties: {
|
||||
hs_timestamp: Date.now().toString(),
|
||||
hs_meeting_title: event.title,
|
||||
hs_meeting_body: this.getHubspotMeetingBody(event),
|
||||
hs_meeting_location: getLocation(event),
|
||||
hs_meeting_start_time: new Date(event.startTime).toISOString(),
|
||||
hs_meeting_end_time: new Date(event.endTime).toISOString(),
|
||||
hs_meeting_outcome: "SCHEDULED",
|
||||
},
|
||||
};
|
||||
|
||||
return hubspotClient.crm.objects.meetings.basicApi.create(simplePublicObjectInput);
|
||||
};
|
||||
|
||||
private hubspotAssociate = async (meeting: any, contacts: any) => {
|
||||
const batchInputPublicAssociation: BatchInputPublicAssociation = {
|
||||
inputs: contacts.map((contact: any) => ({
|
||||
_from: { id: meeting.id },
|
||||
to: { id: contact.id },
|
||||
type: "meeting_event_to_contact",
|
||||
})),
|
||||
};
|
||||
return hubspotClient.crm.associations.batchApi.create(
|
||||
"meetings",
|
||||
"contacts",
|
||||
batchInputPublicAssociation
|
||||
);
|
||||
};
|
||||
|
||||
private hubspotUpdateMeeting = async (uid: string, event: CalendarEvent) => {
|
||||
const simplePublicObjectInput: SimplePublicObjectInput = {
|
||||
properties: {
|
||||
hs_timestamp: Date.now().toString(),
|
||||
hs_meeting_title: event.title,
|
||||
hs_meeting_body: this.getHubspotMeetingBody(event),
|
||||
hs_meeting_location: getLocation(event),
|
||||
hs_meeting_start_time: new Date(event.startTime).toISOString(),
|
||||
hs_meeting_end_time: new Date(event.endTime).toISOString(),
|
||||
hs_meeting_outcome: "RESCHEDULED",
|
||||
},
|
||||
};
|
||||
|
||||
return hubspotClient.crm.objects.meetings.basicApi.update(uid, simplePublicObjectInput);
|
||||
};
|
||||
|
||||
private hubspotDeleteMeeting = async (uid: string) => {
|
||||
return hubspotClient.crm.objects.meetings.basicApi.archive(uid);
|
||||
};
|
||||
|
||||
private hubspotAuth = (credential: Credential) => {
|
||||
const credentialKey = credential.key as unknown as HubspotToken;
|
||||
const isTokenValid = (token: HubspotToken) =>
|
||||
token &&
|
||||
token.tokenType &&
|
||||
token.accessToken &&
|
||||
token.expiryDate &&
|
||||
(token.expiresIn || token.expiryDate) < Date.now();
|
||||
|
||||
const refreshAccessToken = async (refreshToken: string) => {
|
||||
try {
|
||||
const hubspotRefreshToken: HubspotToken = await hubspotClient.oauth.tokensApi.createToken(
|
||||
"refresh_token",
|
||||
undefined,
|
||||
WEBAPP_URL + "/api/integrations/hubspotothercalendar/callback",
|
||||
client_id,
|
||||
client_secret,
|
||||
refreshToken
|
||||
);
|
||||
|
||||
// set expiry date as offset from current time.
|
||||
hubspotRefreshToken.expiryDate = Math.round(Date.now() + hubspotRefreshToken.expiresIn * 1000);
|
||||
await prisma.credential.update({
|
||||
where: {
|
||||
id: credential.id,
|
||||
},
|
||||
data: {
|
||||
key: hubspotRefreshToken as any,
|
||||
},
|
||||
});
|
||||
|
||||
hubspotClient.setAccessToken(hubspotRefreshToken.accessToken);
|
||||
} catch (e: unknown) {
|
||||
this.log.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getToken: () =>
|
||||
!isTokenValid(credentialKey) ? Promise.resolve([]) : refreshAccessToken(credentialKey.refreshToken),
|
||||
};
|
||||
};
|
||||
|
||||
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
|
||||
await this.auth.getToken();
|
||||
const contacts = await this.hubspotContactSearch(event);
|
||||
if (contacts) {
|
||||
const meetingEvent = await this.hubspotCreateMeeting(event);
|
||||
if (meetingEvent) {
|
||||
const associatedMeeting = await this.hubspotAssociate(meetingEvent, contacts);
|
||||
if (associatedMeeting) {
|
||||
return Promise.resolve({
|
||||
uid: meetingEvent.id,
|
||||
id: meetingEvent.id,
|
||||
type: "hubspot_other_calendar",
|
||||
password: "",
|
||||
url: "",
|
||||
additionalInfo: { contacts, associatedMeeting },
|
||||
});
|
||||
}
|
||||
return Promise.reject("Something went wrong when associating the meeting and attendees in HubSpot");
|
||||
}
|
||||
return Promise.reject("Something went wrong when creating a meeting in HubSpot");
|
||||
}
|
||||
return Promise.reject("Something went wrong when searching the atendee in HubSpot");
|
||||
}
|
||||
|
||||
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
|
||||
await this.auth.getToken();
|
||||
return await this.hubspotUpdateMeeting(uid, event);
|
||||
}
|
||||
|
||||
async deleteEvent(uid: string): Promise<void> {
|
||||
await this.auth.getToken();
|
||||
return await this.hubspotDeleteMeeting(uid);
|
||||
}
|
||||
|
||||
async getAvailability(
|
||||
dateFrom: string,
|
||||
dateTo: string,
|
||||
selectedCalendars: IntegrationCalendar[]
|
||||
): Promise<EventBusyDate[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async listCalendars(event?: CalendarEvent): Promise<IntegrationCalendar[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
1
packages/app-store/hubspotothercalendar/lib/index.ts
Normal file
1
packages/app-store/hubspotothercalendar/lib/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as CalendarService } from "./CalendarService";
|
15
packages/app-store/hubspotothercalendar/package.json
Normal file
15
packages/app-store/hubspotothercalendar/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"private": true,
|
||||
"name": "@calcom/hubspotothercalendar",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"description": "HubSpot is a cloud-based CRM designed to help align sales and marketing teams, foster sales enablement, boost ROI and optimize your inbound marketing strategy to generate more, qualified leads.",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*",
|
||||
"@calcom/prisma": "*",
|
||||
"@hubspot/api-client": "^6.0.1-beta4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
}
|
||||
}
|
BIN
packages/app-store/hubspotothercalendar/static/hubspot01.webp
Normal file
BIN
packages/app-store/hubspotothercalendar/static/hubspot01.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
1
packages/app-store/hubspotothercalendar/static/icon.svg
Normal file
1
packages/app-store/hubspotothercalendar/static/icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="512px" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve"><g id="_x31_68-hubspot"><g><path d="M266.197,216.109c-22.551,21.293-36.655,51.48-36.655,84.991c0,26.326,8.714,50.582,23.359,70.08 l-44.473,44.74c-3.953-1.438-8.176-2.245-12.579-2.245c-9.702,0-18.776,3.774-25.605,10.602 c-6.828,6.827-10.602,15.989-10.602,25.696c0,9.701,3.773,18.775,10.602,25.605c6.829,6.826,15.993,10.42,25.605,10.42 c9.703,0,18.777-3.505,25.695-10.42c6.829-6.83,10.602-15.994,10.602-25.605c0-3.774-0.538-7.369-1.707-10.873l44.923-45.102 c19.765,15.183,44.381,24.169,71.244,24.169c64.599,0,116.797-52.38,116.797-116.977c0-58.578-42.854-107.093-99.007-115.628 v-55.343c15.723-6.65,25.335-21.384,25.335-38.545c0-23.449-18.777-43.034-42.227-43.034c-23.448,0-41.956,19.585-41.956,43.034 c0,17.161,9.613,31.895,25.335,38.545v54.983c-13.655,1.887-26.593,6.019-38.362,12.219 c-24.796-18.778-105.565-76.997-151.746-112.126c1.078-3.953,1.798-8.085,1.798-12.397c0-25.875-21.113-46.898-47.078-46.898 c-25.875,0-46.898,21.023-46.898,46.898c0,25.965,21.023,46.988,46.898,46.988c8.805,0,16.98-2.606,24.078-6.828L266.197,216.109z M346.606,363.095c-34.229,0-61.991-27.763-61.991-61.994c0-34.229,27.762-61.99,61.991-61.99c34.23,0,61.992,27.761,61.992,61.99 C408.599,335.332,380.837,363.095,346.606,363.095z" style="fill:#FF7A59;"/></g></g><g id="Layer_1"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -4,6 +4,7 @@ import * as caldavcalendar from "./caldavcalendar";
|
|||
import * as dailyvideo from "./dailyvideo";
|
||||
import * as googlecalendar from "./googlecalendar";
|
||||
import * as googlevideo from "./googlevideo";
|
||||
import * as hubspotothercalendar from "./hubspotothercalendar";
|
||||
import * as huddle01video from "./huddle01video";
|
||||
import * as jitsivideo from "./jitsivideo";
|
||||
import * as office365calendar from "./office365calendar";
|
||||
|
@ -21,6 +22,7 @@ const appStore = {
|
|||
dailyvideo,
|
||||
googlecalendar,
|
||||
googlevideo,
|
||||
hubspotothercalendar,
|
||||
huddle01video,
|
||||
jitsivideo,
|
||||
office365calendar,
|
||||
|
|
|
@ -107,6 +107,7 @@ export const createEvent = async (credential: Credential, calEvent: CalendarEven
|
|||
calEvent.additionalNotes = "Notes have been hidden by the organiser"; // TODO: i18n this string?
|
||||
}
|
||||
|
||||
// TODO: Surfice success/error messages coming from apps to improve end user visibility
|
||||
const creationResult = calendar
|
||||
? await calendar.createEvent(calEvent).catch((e) => {
|
||||
log.error("createEvent failed", e, calEvent);
|
||||
|
|
5
packages/types/App.d.ts
vendored
5
packages/types/App.d.ts
vendored
|
@ -19,7 +19,8 @@ export interface App {
|
|||
| `${string}_payment`
|
||||
| `${string}_video`
|
||||
| `${string}_web3`
|
||||
| `${string}_other`;
|
||||
| `${string}_other`
|
||||
| `${string}_other_calendar`;
|
||||
/** The display name for the app, TODO settle between this or name */
|
||||
title: string;
|
||||
/** The display name for the app */
|
||||
|
@ -29,7 +30,7 @@ export interface App {
|
|||
/** The icon to display in /apps/installed */
|
||||
imageSrc: string;
|
||||
/** TODO determine if we should use this instead of category */
|
||||
variant: "calendar" | "payment" | "conferencing" | "other";
|
||||
variant: "calendar" | "payment" | "conferencing" | "other" | "other_calendar";
|
||||
/** The slug for the app store public page inside `/apps/[slug] */
|
||||
slug: string;
|
||||
/** The category to which this app belongs, currently we have `calendar`, `payment` or `video` */
|
||||
|
|
Loading…
Reference in a new issue