Files
calendar/apps/web/lib/plain/plainChat.tsx
T
+7 225313e391 feat: Plain chat (#18284)
* plain custom desin

* No display on card

* dynamic plain chat component and hmac hash

* re-route users to plain.com chat instead of intercom chat when going to /support

* provider errors

* yarn lock fix

* plain chat removed unneeded hmac

* fix ts error

* error handling improved

* remove intercome provider from app-dir

* Create getting-started.mdx (#18342)

* Delete help directory (#18343)

* chore: moved docs/help to /help (#18345)

* chore: Delete unused guides directory (#18346)

* feat: booking filters (#18303)

Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>

* chore: Remove `HeadSeo` components where no longer needed + improve app router metadata logic (#18348)

* remove HeadSeo for already migrated pages and refactor prepareMetadata

* create _generateMetadataWithoutImage and refactor _generateMetadata

* chore: app router - /bookings status page (#18183)

* chore: app router - /bookings page

* remove env vars

* fix

* Update middleware.ts

* revert unneeded change

* refactor for the better

* fix

* cache i18n instances (#18309)

* feat: virtual queues tab in insights (#18260)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>

* feat: update translations via @replexica (#18361)

Co-authored-by: Replexica <support@replexica.com>

* update OOO e2e tests to remove flakiness (#18367)

* fix: metadata is overwritten for child managed eventType when updating parent (#18059)

* fix: metadata is overwirten

* Update

* type error

* Update

* fix test

* fix type error

* chore: added routing support link (#18369)

* Added routing form support link

* small change

* Type fix

---------

Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>

* chore: refactor handling logic for embeds in app router (#18362)

* refactor handling logic for embeds in app router

* fix type checks

* add test for withEmbedSsrAppDir

* fix

* review changes

* yarn lock

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Calcom Bot <109866826+calcom-bot@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Replexica <support@replexica.com>
Co-authored-by: Vijay <vijayraghav22@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-12-26 19:07:08 +00:00

248 lines
6.5 KiB
TypeScript

"use client";
import { useSession } from "next-auth/react";
import { usePathname, useSearchParams } from "next/navigation";
import Script from "next/script";
import { useEffect, useState } from "react";
declare global {
interface Window {
Plain?: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
init: (config: any) => void;
open: () => void;
};
plainScriptLoaded?: () => void;
__PLAIN_CONFIG__?: PlainChatConfig;
}
}
interface PlainChatConfig {
appId: string;
customerDetails: {
email: string;
emailHash: string;
fullName: string;
shortName: string;
chatAvatarUrl: string;
};
links: Array<{
icon: string;
text: string;
url: string;
}>;
chatButtons: Array<{
icon: string;
text: string;
type: string;
form?: {
fields: Array<{
type: string;
placeholder: string;
options: Array<{
icon: string;
text: string;
threadDetails: {
severity: string;
labelTypeIds: Array<string>;
issueType: string;
priority: string;
};
}>;
}>;
};
}>;
entryPoint: {
type: string;
};
hideBranding: boolean;
theme: string;
style: {
brandColor: string;
launcherBackgroundColor: string;
launcherIconColor: string;
};
position: {
bottom: string;
right: string;
};
}
const PlainChat = () => {
const [config, setConfig] = useState<PlainChatConfig | null>(null);
const { data: session } = useSession();
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
const initConfig = async () => {
if (!session?.user?.email) return;
try {
const response = await fetch("/api/plain-hash", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to generate hash: ${errorText}`);
}
const data = await response.json();
if (!data.hash || !data.email || !data.appId) {
throw new Error("Missing required fields in API response");
}
const plainChatConfig: PlainChatConfig = {
appId: data.appId,
customerDetails: {
email: data.email,
shortName: data.shortName,
fullName: data.fullName,
emailHash: data.hash,
chatAvatarUrl: data.chatAvatarUrl,
},
links: [
{
icon: "book",
text: "Documentation",
url: "https://cal.com/docs",
},
{
icon: "chat",
text: "Ask the community",
url: "https://github.com/calcom/cal.com/discussions",
},
],
chatButtons: [
{
icon: "chat",
text: "Ask a question",
type: "primary",
},
{
icon: "bulb",
text: "Send feedback",
type: "default",
},
{
icon: "error",
text: "Report an issue",
type: "default",
form: {
fields: [
{
type: "dropdown",
placeholder: "Select severity...",
options: [
{
icon: "support",
text: "I'm unable to use the app",
threadDetails: {
severity: "critical",
issueType: "critical",
labelTypeIds: ["lt_01JFJWNWAC464N8DZ6YE71YJRF"],
priority: "u",
},
},
{
icon: "error",
text: "Major functionality degraded",
threadDetails: {
severity: "major",
issueType: "major",
labelTypeIds: ["lt_01JFJWP3KECF1YQES6XF212RFW"],
priority: "h",
},
},
{
icon: "bug",
text: "Minor annoyance",
threadDetails: {
severity: "minor",
issueType: "minor",
labelTypeIds: ["lt_01JFJWPC8ADW0PK28JHMJR6NSS"],
priority: "l",
},
},
],
},
],
},
},
],
entryPoint: {
type: "chat",
},
hideBranding: true,
theme: "auto",
style: {
brandColor: "#FFFFFF",
launcherBackgroundColor: "#262626",
launcherIconColor: "#FFFFFF",
},
position: {
bottom: "20px",
right: "20px",
},
};
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
window.__PLAIN_CONFIG__ = plainChatConfig;
}
setConfig(plainChatConfig);
if (pathname === "/event-types" && searchParams?.has("openPlain")) {
const timer = setTimeout(() => {
if (window.Plain) {
window.Plain.open();
}
}, 100);
return () => clearTimeout(timer);
}
} catch (error) {
console.error("Failed to initialize Plain Chat:", error);
}
};
initConfig();
}, [session, pathname, searchParams]);
const plainChatScript = `
window.plainScriptLoaded = function() {
if (window.Plain && ${Boolean(config)}) {
try {
Plain.init(${config ? JSON.stringify(config) : null});
} catch (error) {
console.error("Failed to initialize Plain:", error);
}
}
}
`;
if (!config) return null;
return (
<>
<Script
id="plain-chat"
src="https://chat.cdn-plain.com/index.js"
strategy="afterInteractive"
onLoad={() => window.plainScriptLoaded?.()}
/>
<Script
id="plain-chat-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{ __html: plainChatScript }}
/>
</>
);
};
export default PlainChat;