Files
calendar/packages/features/ee/support/lib/intercom/intercom.ts
T
Peer RichelsenGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Amit Sharmasean-brydoncoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
20b7ac8c0d feat: implement tiered Intercom chat system replacing Plain (#23285)
* feat: implement tiered Intercom chat system replacing Plain

- Add TieredIntercomChat component with customer tier detection
- Add IntercomContactForm for free users using Intercom API
- Add /api/intercom-conversation endpoint for free user support
- Update UserDropdown to use tiered chat logic
- Replace Plain chat with Intercom in app providers
- Remove all Plain chat components and related files
- Use useHasPaidPlan hook for customer tier detection

Paying customers see Intercom widget, free users see contact form
that creates conversations via Intercom API.

Co-Authored-By: peer@cal.com <peer@cal.com>

* fix: add missing environment variables to turbo.json globalEnv

- Add NEXT_PUBLIC_WEBAPP_URL, NEXT_PUBLIC_WEBSITE_URL, NEXT_PUBLIC_STRIPE_PUBLIC_KEY
- Add NEXT_PUBLIC_IS_E2E, NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE_MONTHLY
- Add NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD, NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID
- Resolves turbo/no-undeclared-env-vars ESLint errors blocking commits

Co-Authored-By: peer@cal.com <peer@cal.com>

* fix: resolve linting errors in platform examples base package

- Fix prettier/prettier formatting (quotes and comma)
- Add underscore prefix to unused variables
- Resolves CI failure in @calcom/base#lint check

Co-Authored-By: peer@cal.com <peer@cal.com>

* remove plain usage

* placement fixes

* fix: pop closing immediately issue

* stuff

* proper error and additional user info

* add key to .env.example and remove unused plain route

* fix conversation route

* refactor intercom dynamic provider

* code rabbit fixes

* feat: introduce tiered support feature flag

* fix: type check

* Apply suggestion from @coderabbitai[bot]

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-09 18:20:22 +09:00

143 lines
3.7 KiB
TypeScript

import { safeStringify } from "@calcom/lib/safeStringify";
export interface Contact extends CreateContact {
id: string;
}
interface CreateContact {
type: "contact" | "lead";
external_id: string;
email: string;
name: string;
custom_attributes?: object;
}
interface IntercomResponse<T> {
data?: T;
error?: string;
}
interface CreateConversation {
from: {
type: string;
id: string;
};
body: string;
}
const INTERCOM_API_ENDPOINT = "https://api.intercom.io";
const INTERCOM_API_TOKEN = process.env.INTERCOM_API_TOKEN;
export const intercom = {
async getContactByEmail(email: string): Promise<IntercomResponse<Contact | null>> {
try {
const res = await fetch(`${INTERCOM_API_ENDPOINT}/contacts/search`, {
method: "POST",
body: JSON.stringify({
query: {
operator: "AND",
value: [
{
field: "email",
operator: "=",
value: email,
},
],
},
pagination: {
per_page: 1,
},
}),
headers: {
Authorization: `Bearer ${INTERCOM_API_TOKEN}`,
"Content-Type": "application/json",
},
});
if (!res.ok) {
const body = await res.json();
return {
error: body?.errors[0]?.message,
};
}
const { data } = (await res.json()) as { type: string; data: Contact[] };
if (data.length === 0) {
return {
data: null,
};
}
return {
data: data[0],
};
} catch (err) {
console.error(`Unexpected error while fetching contact data from email: ${safeStringify(err)}`);
return {
error: "Unexpected error while fetching contact data from email",
};
}
},
async createContact(data: CreateContact): Promise<IntercomResponse<Contact>> {
try {
const res = await fetch(`${INTERCOM_API_ENDPOINT}/contacts`, {
method: "POST",
headers: {
Authorization: `Bearer ${INTERCOM_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!res.ok) {
const data = await res.json();
console.error(`Error creating contact from email ${data.email}: `, safeStringify(data));
return {
error: data?.errors[0]?.message ?? "Error creating contact from email",
};
}
const contact = (await res.json()) as Contact;
return {
data: contact,
};
} catch (err) {
console.error(`Unexpected error while creating contact from email: ${safeStringify(err)}`);
return {
error: "Unexpected error while creating contact from email",
};
}
},
async createConversation({ from, body }: CreateConversation): Promise<IntercomResponse<boolean>> {
try {
const response = await fetch(`https://api.intercom.io/conversations`, {
method: "POST",
headers: {
Authorization: `Bearer ${INTERCOM_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from,
body,
}),
});
if (!response.ok) {
const errorData = await response.json();
console.error("Intercom API error:", safeStringify(errorData));
return {
error: `Intercom Err: ${errorData?.errors?.[0].message ?? "Error creating conversation"}`,
};
}
return {
data: true,
};
} catch (err) {
console.error(`Unexpected error while creating conversation from email: ${safeStringify(err)}`);
return {
error: "Unexpected error while creating contact from email",
};
}
},
};