* 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>
143 lines
3.7 KiB
TypeScript
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",
|
|
};
|
|
}
|
|
},
|
|
};
|