* 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>
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
import { cookies, headers } from "next/headers";
|
|
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
|
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
import type { Contact } from "@calcom/features/ee/support/lib/intercom/intercom";
|
|
import { intercom } from "@calcom/features/ee/support/lib/intercom/intercom";
|
|
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import { UserRepository } from "@calcom/lib/server/repository/user";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
|
|
|
|
const log = logger.getSubLogger({ prefix: [`/api/support/conversation`] });
|
|
|
|
export async function POST(req: NextRequest) {
|
|
const headersList = await headers();
|
|
const cookiesList = await cookies();
|
|
const legacyReq = buildLegacyRequest(headersList, cookiesList);
|
|
|
|
const session = await getServerSession({ req: legacyReq });
|
|
if (!session?.user?.email) {
|
|
return NextResponse.json({ error: "Unauthorized - No session found" }, { status: 401 });
|
|
}
|
|
|
|
const intercomApiKey = process.env.INTERCOM_API_TOKEN;
|
|
if (!intercomApiKey) {
|
|
return NextResponse.json({ error: "Intercom API key not configured" }, { status: 500 });
|
|
}
|
|
|
|
let contact: Contact;
|
|
|
|
const existingContact = await intercom.getContactByEmail(session.user.email);
|
|
|
|
if (existingContact.error) {
|
|
return NextResponse.json(
|
|
{ error: existingContact?.error ?? "Error fetching intercom contact for user" },
|
|
{ status: 502 }
|
|
);
|
|
}
|
|
|
|
const { user } = session;
|
|
|
|
if (!existingContact.data) {
|
|
const additionalUserInfo = await new UserRepository(prisma).getUserStats({ userId: session.user.id });
|
|
const sumOfTeamEventTypes = additionalUserInfo?.teams.reduce(
|
|
(sum, team) => sum + team.team.eventTypes.length,
|
|
0
|
|
);
|
|
|
|
const newContact = await intercom.createContact({
|
|
email: session.user.email,
|
|
external_id: session.user.id.toString(),
|
|
name: session.user.name ?? session.user.email,
|
|
type: "contact",
|
|
custom_attributes: {
|
|
user_name: user?.username,
|
|
link: `${WEBSITE_URL}/${encodeURIComponent(user?.username ?? "")}`,
|
|
admin_link: `${WEBAPP_URL}/settings/admin/users/${user?.id}/edit`,
|
|
impersonate_user: `${WEBAPP_URL}/settings/admin/impersonation?username=${encodeURIComponent(
|
|
user?.email ?? user?.username ?? ""
|
|
)}`,
|
|
locale: user?.locale,
|
|
completed_onboarding: user?.completedOnboarding,
|
|
is_logged_in: !!user,
|
|
has_orgs_plan: !!user?.org,
|
|
organization: user?.org?.slug,
|
|
sum_of_bookings: additionalUserInfo?._count?.bookings,
|
|
sum_of_calendars: additionalUserInfo?._count?.userLevelSelectedCalendars,
|
|
sum_of_teams: additionalUserInfo?._count?.teams,
|
|
sum_of_event_types: additionalUserInfo?._count?.eventTypes,
|
|
sum_of_team_event_types: sumOfTeamEventTypes,
|
|
},
|
|
});
|
|
|
|
if (newContact.error || !newContact.data) {
|
|
return NextResponse.json(
|
|
{ error: newContact.error ?? "Error creating contact from email" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
contact = newContact.data;
|
|
} else {
|
|
contact = existingContact.data;
|
|
}
|
|
|
|
if (!contact) {
|
|
return NextResponse.json({ error: "No contactId found" }, { status: 404 });
|
|
}
|
|
|
|
try {
|
|
const { message } = await req.json();
|
|
|
|
if (!message.trim()) {
|
|
return NextResponse.json({ error: "Cannot start a conversation without message" }, { status: 400 });
|
|
}
|
|
|
|
const conversation = await intercom.createConversation({
|
|
body: message,
|
|
from: {
|
|
id: contact.id,
|
|
type: "user",
|
|
},
|
|
});
|
|
|
|
if (conversation.error) {
|
|
return NextResponse.json(
|
|
{ error: conversation.error ?? "Error creating conversation" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
return NextResponse.json({ message: "Created conversation in Intercom" }, { status: 200 });
|
|
} catch (err) {
|
|
console.error(err);
|
|
log.error(`Error creating Intercom conversation:`, safeStringify(err));
|
|
return NextResponse.json({ error: "Unexpected error occurred" }, { status: 500 });
|
|
}
|
|
}
|