diff --git a/apps/web/pages/apps/installed/[category].tsx b/apps/web/pages/apps/installed/[category].tsx
index f0f54e8d07..009a96f0e8 100644
--- a/apps/web/pages/apps/installed/[category].tsx
+++ b/apps/web/pages/apps/installed/[category].tsx
@@ -2,6 +2,7 @@
import { useReducer } from "react";
+import getAppCategoryTitle from "@calcom/app-store/_utils/getAppCategoryTitle";
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -57,11 +58,13 @@ const IntegrationsContainer = ({
customLoader={}
success={({ data }) => {
if (!data.items.length) {
+ const emptyHeaderCategory = getAppCategoryTitle(variant || "other", true);
+
return (
{
await prisma.credential.create({
data: {
type: "alby_payment",
+ appId: "alby",
userId: user.id,
key: {
account_id: "random",
@@ -57,6 +58,7 @@ test.describe("Payment app", () => {
await prisma.credential.create({
data: {
type: "stripe_payment",
+ appId: "stripe",
userId: user.id,
key: {
scope: "read_write",
@@ -101,6 +103,7 @@ test.describe("Payment app", () => {
await prisma.credential.create({
data: {
type: "paypal_payment",
+ appId: "paypal",
userId: user.id,
key: {
client_id: "randomString",
@@ -147,6 +150,7 @@ test.describe("Payment app", () => {
await prisma.credential.create({
data: {
type: "alby_payment",
+ appId: "alby",
userId: user.id,
key: {},
},
@@ -173,6 +177,7 @@ test.describe("Payment app", () => {
await prisma.credential.create({
data: {
type: "paypal_payment",
+ appId: "paypal",
userId: user.id,
key: {},
},
@@ -234,6 +239,7 @@ test.describe("Payment app", () => {
data: [
{
type: "paypal_payment",
+ appId: "paypal",
userId: user.id,
key: {
client_id: "randomString",
@@ -243,6 +249,7 @@ test.describe("Payment app", () => {
},
{
type: "stripe_payment",
+ appId: "stripe",
userId: user.id,
key: {
scope: "read_write",
@@ -279,6 +286,7 @@ test.describe("Payment app", () => {
data: [
{
type: "paypal_payment",
+ appId: "paypal",
userId: user.id,
key: {
client_id: "randomString",
@@ -288,6 +296,7 @@ test.describe("Payment app", () => {
},
{
type: "stripe_payment",
+ appId: "stripe",
userId: user.id,
key: {
scope: "read_write",
diff --git a/packages/app-store/_utils/getAppCategoryTitle.ts b/packages/app-store/_utils/getAppCategoryTitle.ts
new file mode 100644
index 0000000000..25eb73cc08
--- /dev/null
+++ b/packages/app-store/_utils/getAppCategoryTitle.ts
@@ -0,0 +1,22 @@
+import type { AppCategories } from "@calcom/prisma/enums";
+
+/**
+ * Handles if the app category should be full capitalized ex. CRM
+ *
+ * @param {App["variant"]} variant - The variant of the app.
+ * @param {boolean} [returnLowerCase] - Optional flag to return the title in lowercase.
+ */
+const getAppCategoryTitle = (variant: AppCategories, returnLowerCase?: boolean) => {
+ let title: string;
+
+ if (variant === "crm") {
+ title = "CRM";
+ return title;
+ } else {
+ title = variant;
+ }
+
+ return returnLowerCase ? title.toLowerCase() : title;
+};
+
+export default getAppCategoryTitle;
diff --git a/packages/app-store/_utils/getCalendar.ts b/packages/app-store/_utils/getCalendar.ts
index 8192db1d33..73a9292609 100644
--- a/packages/app-store/_utils/getCalendar.ts
+++ b/packages/app-store/_utils/getCalendar.ts
@@ -29,6 +29,11 @@ export const getCalendar = async (credential: CredentialPayload | null): Promise
if (calendarType?.endsWith("_other_calendar")) {
calendarType = calendarType.split("_other_calendar")[0];
}
+ // Backwards compatibility until CRM manager is created
+ if (calendarType?.endsWith("_crm")) {
+ calendarType = calendarType.split("_crm")[0];
+ }
+
const calendarAppImportFn = appStore[calendarType.split("_").join("") as keyof typeof appStore];
if (!calendarAppImportFn) {
diff --git a/packages/app-store/closecom/api/_postAdd.ts b/packages/app-store/closecom/api/_postAdd.ts
index f1fff19895..8c96fd57d4 100644
--- a/packages/app-store/closecom/api/_postAdd.ts
+++ b/packages/app-store/closecom/api/_postAdd.ts
@@ -8,6 +8,7 @@ import prisma from "@calcom/prisma";
import checkSession from "../../_utils/auth";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
+import appConfig from "../config.json";
export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
const session = checkSession(req);
@@ -18,10 +19,10 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
const encrypted = symmetricEncrypt(JSON.stringify({ api_key }), process.env.CALENDSO_ENCRYPTION_KEY || "");
const data = {
- type: "closecom_other_calendar",
+ type: appConfig.type,
key: { encrypted },
userId: session.user?.id,
- appId: "closecom",
+ appId: appConfig.slug,
};
try {
diff --git a/packages/app-store/closecom/config.json b/packages/app-store/closecom/config.json
index 5d2c596abe..58a46a86e9 100644
--- a/packages/app-store/closecom/config.json
+++ b/packages/app-store/closecom/config.json
@@ -3,10 +3,10 @@
"name": "Close.com",
"title": "Close.com",
"slug": "closecom",
- "type": "closecom_other_calendar",
+ "type": "closecom_crm",
"logo": "icon.svg",
"url": "https://cal.com/",
- "variant": "other",
+ "variant": "crm",
"categories": ["crm"],
"publisher": "Cal.com, Inc.",
"email": "help@cal.com",
diff --git a/packages/app-store/hubspot/_metadata.ts b/packages/app-store/hubspot/_metadata.ts
index c2aaa8deb7..ea5379a233 100644
--- a/packages/app-store/hubspot/_metadata.ts
+++ b/packages/app-store/hubspot/_metadata.ts
@@ -6,8 +6,8 @@ export const metadata = {
name: "HubSpot CRM",
installed: !!process.env.HUBSPOT_CLIENT_ID,
description: _package.description,
- type: "hubspot_other_calendar",
- variant: "other_calendar",
+ type: "hubspot_crm",
+ variant: "crm",
logo: "icon.svg",
publisher: "Cal.com",
url: "https://hubspot.com/",
diff --git a/packages/app-store/hubspot/api/add.ts b/packages/app-store/hubspot/api/add.ts
index 9d205e01fb..1e8d6a4f52 100644
--- a/packages/app-store/hubspot/api/add.ts
+++ b/packages/app-store/hubspot/api/add.ts
@@ -1,7 +1,7 @@
import * as hubspot from "@hubspot/api-client";
import type { NextApiRequest, NextApiResponse } from "next";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
@@ -18,7 +18,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
if (!client_id) return res.status(400).json({ message: "HubSpot client id missing." });
- const redirectUri = `${WEBAPP_URL}/api/integrations/hubspot/callback`;
+ const redirectUri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/hubspot/callback`;
const url = hubspotClient.oauth.getAuthorizationUrl(
client_id,
redirectUri,
diff --git a/packages/app-store/hubspot/api/callback.ts b/packages/app-store/hubspot/api/callback.ts
index c43253fa69..14016534b8 100644
--- a/packages/app-store/hubspot/api/callback.ts
+++ b/packages/app-store/hubspot/api/callback.ts
@@ -2,13 +2,14 @@ import * as hubspot from "@hubspot/api-client";
import type { TokenResponseIF } from "@hubspot/api-client/lib/codegen/oauth/models/TokenResponseIF";
import type { NextApiRequest, NextApiResponse } from "next";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { WEBAPP_URL_FOR_OAUTH } 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 metadata from "../_metadata";
let client_id = "";
let client_secret = "";
@@ -39,7 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const hubspotToken: HubspotToken = await hubspotClient.oauth.tokensApi.createToken(
"authorization_code",
code,
- `${WEBAPP_URL}/api/integrations/hubspot/callback`,
+ `${WEBAPP_URL_FOR_OAUTH}/api/integrations/hubspot/callback`,
client_id,
client_secret
);
@@ -47,7 +48,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// set expiry date as offset from current time.
hubspotToken.expiryDate = Math.round(Date.now() + hubspotToken.expiresIn * 1000);
- await createOAuthAppCredential({ appId: "hubspot", type: "hubspot_other_calendar" }, hubspotToken, req);
+ await createOAuthAppCredential({ appId: metadata.slug, type: metadata.type }, hubspotToken, req);
const state = decodeOAuthState(req);
res.redirect(
diff --git a/packages/app-store/pipedrive-crm/config.json b/packages/app-store/pipedrive-crm/config.json
index ff390101ba..013e361fa9 100644
--- a/packages/app-store/pipedrive-crm/config.json
+++ b/packages/app-store/pipedrive-crm/config.json
@@ -2,7 +2,7 @@
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Pipedrive CRM",
"slug": "pipedrive-crm",
- "type": "pipedrive-crm_other_calendar",
+ "type": "pipedrive-crm_crm",
"logo": "icon.svg",
"url": "https://revert.dev",
"variant": "crm",
diff --git a/packages/app-store/salesforce/api/add.ts b/packages/app-store/salesforce/api/add.ts
index 907afc723a..2d1ac45930 100644
--- a/packages/app-store/salesforce/api/add.ts
+++ b/packages/app-store/salesforce/api/add.ts
@@ -1,7 +1,7 @@
import jsforce from "jsforce";
import type { NextApiRequest, NextApiResponse } from "next";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
@@ -17,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const salesforceClient = new jsforce.Connection({
clientId: consumer_key,
- redirectUri: `${WEBAPP_URL}/api/integrations/salesforce/callback`,
+ redirectUri: `${WEBAPP_URL_FOR_OAUTH}/api/integrations/salesforce/callback`,
});
const url = salesforceClient.oauth2.getAuthorizationUrl({
diff --git a/packages/app-store/salesforce/api/callback.ts b/packages/app-store/salesforce/api/callback.ts
index 7c9db80278..aad019a560 100644
--- a/packages/app-store/salesforce/api/callback.ts
+++ b/packages/app-store/salesforce/api/callback.ts
@@ -1,13 +1,14 @@
import jsforce from "jsforce";
import type { NextApiRequest, NextApiResponse } from "next";
-import { WEBAPP_URL } from "@calcom/lib/constants";
+import { WEBAPP_URL_FOR_OAUTH } 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";
let consumer_key = "";
let consumer_secret = "";
@@ -33,16 +34,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const conn = new jsforce.Connection({
clientId: consumer_key,
clientSecret: consumer_secret,
- redirectUri: `${WEBAPP_URL}/api/integrations/salesforce/callback`,
+ redirectUri: `${WEBAPP_URL_FOR_OAUTH}/api/integrations/salesforce/callback`,
});
const salesforceTokenInfo = await conn.oauth2.requestToken(code as string);
- await createOAuthAppCredential(
- { appId: "salesforce", type: "salesforce_other_calendar" },
- salesforceTokenInfo,
- req
- );
+ await createOAuthAppCredential({ appId: appConfig.slug, type: appConfig.type }, salesforceTokenInfo, req);
const state = decodeOAuthState(req);
res.redirect(
diff --git a/packages/app-store/salesforce/config.json b/packages/app-store/salesforce/config.json
index 5495002b97..3f95a3c9b0 100644
--- a/packages/app-store/salesforce/config.json
+++ b/packages/app-store/salesforce/config.json
@@ -2,10 +2,10 @@
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Salesforce",
"slug": "salesforce",
- "type": "salesforce_other_calendar",
+ "type": "salesforce_crm",
"logo": "icon.png",
"url": "https://cal.com/",
- "variant": "other_calendar",
+ "variant": "crm",
"categories": ["crm"],
"publisher": "Cal.com, Inc.",
"email": "help@cal.com",
diff --git a/packages/app-store/sendgrid/config.json b/packages/app-store/sendgrid/config.json
index 19d911f770..3f15a92402 100644
--- a/packages/app-store/sendgrid/config.json
+++ b/packages/app-store/sendgrid/config.json
@@ -6,7 +6,7 @@
"logo": "logo.png",
"url": "https://cal.com/",
"variant": "other_calendar",
- "categories": ["crm"],
+ "categories": ["other"],
"publisher": "Cal.com, Inc.",
"email": "help@cal.com",
"description": "SendGrid delivers your transactional and marketing emails through the world's largest cloud-based email delivery platform.",
diff --git a/packages/app-store/utils.ts b/packages/app-store/utils.ts
index a358f9d03a..7947f843ed 100644
--- a/packages/app-store/utils.ts
+++ b/packages/app-store/utils.ts
@@ -47,7 +47,7 @@ export const ALL_APPS = Object.values(ALL_APPS_MAP);
*/
function getApps(credentials: CredentialDataWithTeamName[], filterOnCredentials?: boolean) {
const apps = ALL_APPS.reduce((reducedArray, appMeta) => {
- const appCredentials = credentials.filter((credential) => credential.type === appMeta.type);
+ const appCredentials = credentials.filter((credential) => credential.appId === appMeta.slug);
if (filterOnCredentials && !appCredentials.length && !appMeta.isGlobal) return reducedArray;
diff --git a/packages/app-store/zoho-bigin/config.json b/packages/app-store/zoho-bigin/config.json
index 1f11350698..aea58ef2c7 100644
--- a/packages/app-store/zoho-bigin/config.json
+++ b/packages/app-store/zoho-bigin/config.json
@@ -2,11 +2,11 @@
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "Zoho Bigin",
"slug": "zoho-bigin",
- "type": "zoho-bigin_other_calendar",
+ "type": "zoho-bigin_crm",
"logo": "zohobigin.svg",
"url": "https://github.com/ShaneMaglangit",
- "variant": "other",
- "categories": ["other"],
+ "variant": "crm",
+ "categories": ["crm"],
"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.",
diff --git a/packages/app-store/zohocrm/api/callback.ts b/packages/app-store/zohocrm/api/callback.ts
index ff53b2d3ee..9f02f28201 100644
--- a/packages/app-store/zohocrm/api/callback.ts
+++ b/packages/app-store/zohocrm/api/callback.ts
@@ -9,6 +9,7 @@ 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";
let client_id = "";
let client_secret = "";
@@ -51,11 +52,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
zohoCrmTokenInfo.data.expiryDate = Math.round(Date.now() + 60 * 60);
zohoCrmTokenInfo.data.accountServer = req.query["accounts-server"];
- await createOAuthAppCredential(
- { appId: "zohocrm", type: "zohocrm_other_calendar" },
- zohoCrmTokenInfo.data,
- req
- );
+ await createOAuthAppCredential({ appId: appConfig.slug, type: appConfig.type }, zohoCrmTokenInfo.data, req);
const state = decodeOAuthState(req);
res.redirect(
diff --git a/packages/app-store/zohocrm/config.json b/packages/app-store/zohocrm/config.json
index 4166934341..daea3c1162 100644
--- a/packages/app-store/zohocrm/config.json
+++ b/packages/app-store/zohocrm/config.json
@@ -2,10 +2,10 @@
"/*": "Don't modify slug - If required, do it using cli edit command",
"name": "ZohoCRM",
"slug": "zohocrm",
- "type": "zohocrm_other_calendar",
+ "type": "zohocrm_crm",
"logo": "icon.svg",
"url": "https://github.com/jatinsandilya",
- "variant": "other",
+ "variant": "crm",
"categories": ["crm"],
"publisher": "Jatin Sandilya",
"email": "help@cal.com",
diff --git a/packages/core/EventManager.ts b/packages/core/EventManager.ts
index 8bae88b38c..9ec0373680 100644
--- a/packages/core/EventManager.ts
+++ b/packages/core/EventManager.ts
@@ -95,7 +95,10 @@ export default class EventManager {
// This includes all calendar-related apps, traditional calendars such as Google Calendar
// (type google_calendar) and non-traditional calendars such as CRMs like Close.com
// (type closecom_other_calendar)
- this.calendarCredentials = appCredentials.filter((cred) => cred.type.endsWith("_calendar"));
+ this.calendarCredentials = appCredentials.filter(
+ // Backwards compatibility until CRM manager is implemented
+ (cred) => cred.type.endsWith("_calendar") || cred.type.endsWith("_crm")
+ );
this.videoCredentials = appCredentials
.filter((cred) => cred.type.endsWith("_video") || cred.type.endsWith("_conferencing"))
// Whenever a new video connection is added, latest credentials are added with the highest ID.
@@ -518,7 +521,10 @@ export default class EventManager {
* Not ideal but, if we don't find a destination calendar,
* fallback to the first connected calendar - Shouldn't be a CRM calendar
*/
- const [credential] = this.calendarCredentials.filter((cred) => !cred.type.endsWith("other_calendar"));
+ // Backwards compatibility until CRM manager is created
+ const [credential] = this.calendarCredentials.filter(
+ (cred) => !cred.type.endsWith("other_calendar") || !cred.type.endsWith("crm")
+ );
if (credential) {
const createdEvent = await createEvent(credential, event);
log.silly("Created Calendar event", safeStringify({ createdEvent }));
@@ -618,7 +624,8 @@ export default class EventManager {
createdEvents = createdEvents.concat(
await Promise.all(
this.calendarCredentials
- .filter((cred) => cred.type.includes("other_calendar"))
+ // Backwards compatibility until CRM manager is created
+ .filter((cred) => cred.type.includes("other_calendar") || cred.type.includes("crm"))
.map(async (cred) => await createEvent(cred, event))
)
);
@@ -790,7 +797,8 @@ export default class EventManager {
// Taking care of non-traditional calendar integrations
result = result.concat(
this.calendarCredentials
- .filter((cred) => cred.type.includes("other_calendar"))
+ // Backwards compatibility until CRM manager is created
+ .filter((cred) => cred.type.includes("other_calendar") && cred.type.includes("crm"))
.map(async (cred) => {
const calendarReference = booking.references.find((ref) => ref.type === cred.type);
diff --git a/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts b/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts
index 1a4af97b41..8f612fad27 100644
--- a/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts
+++ b/packages/trpc/server/routers/loggedInViewer/integrations.handler.ts
@@ -135,13 +135,13 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
//TODO: Refactor this to pick up only needed fields and prevent more leaking
let apps = await Promise.all(
enabledApps.map(async ({ credentials: _, credential, key: _2 /* don't leak to frontend */, ...app }) => {
- const userCredentialIds = credentials.filter((c) => c.type === app.type && !c.teamId).map((c) => c.id);
+ const userCredentialIds = credentials.filter((c) => c.appId === app.slug && !c.teamId).map((c) => c.id);
const invalidCredentialIds = credentials
- .filter((c) => c.type === app.type && c.invalid)
+ .filter((c) => c.appId === app.slug && c.invalid)
.map((c) => c.id);
const teams = await Promise.all(
credentials
- .filter((c) => c.type === app.type && c.teamId)
+ .filter((c) => c.appId === app.slug && c.teamId)
.map(async (c) => {
const team = userTeams.find((team) => team.id === c.teamId);
if (!team) {
diff --git a/packages/types/App.d.ts b/packages/types/App.d.ts
index 39d9d5f70c..4c81987fc6 100644
--- a/packages/types/App.d.ts
+++ b/packages/types/App.d.ts
@@ -66,6 +66,7 @@ export interface App {
| `${string}_other`
| `${string}_automation`
| `${string}_analytics`
+ | `${string}_crm`
| `${string}_other_calendar`;
/**
@@ -79,7 +80,15 @@ export interface App {
/** A brief description, usually found in the app's package.json */
description: string;
/** TODO determine if we should use this instead of category */
- variant: "calendar" | "payment" | "conferencing" | "video" | "other" | "other_calendar" | "automation";
+ variant:
+ | "calendar"
+ | "payment"
+ | "conferencing"
+ | "video"
+ | "other"
+ | "other_calendar"
+ | "automation"
+ | "crm";
/** The slug for the app store public page inside `/apps/[slug] */
slug: string;