feat: Add fresh chat to list of support integration (#7448)

* chore: add fresh chat configs to env.example

* feat: add fresh chat menu item and render

* refactor: remove some event listeners

* chore: make popover closed after the click

* refactor the code and fix some bugs

* feat: auto open chat

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
pull/6940/head^2
Nafees Nazik 2023-03-08 17:00:24 +05:30 committed by GitHub
parent d28c914d3c
commit 29ceaee8d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 289 additions and 147 deletions

View File

@ -78,6 +78,10 @@ NEXT_PUBLIC_ZENDESK_KEY=
# Help Scout Config # Help Scout Config
NEXT_PUBLIC_HELPSCOUT_KEY= NEXT_PUBLIC_HELPSCOUT_KEY=
# Fresh Chat Config
NEXT_PUBLIC_FRESHCHAT_TOKEN=
NEXT_PUBLIC_FRESHCHAT_HOST=
# Inbox to send user feedback # Inbox to send user feedback
SEND_FEEDBACK_EMAIL= SEND_FEEDBACK_EMAIL=

View File

@ -1,13 +1,20 @@
import FreshChatMenuItem from "../lib/freshchat/FreshChatMenuItem";
import HelpscoutMenuItem from "../lib/helpscout/HelpscoutMenuItem"; import HelpscoutMenuItem from "../lib/helpscout/HelpscoutMenuItem";
import IntercomMenuItem from "../lib/intercom/IntercomMenuItem"; import IntercomMenuItem from "../lib/intercom/IntercomMenuItem";
import ZendeskMenuItem from "../lib/zendesk/ZendeskMenuItem"; import ZendeskMenuItem from "../lib/zendesk/ZendeskMenuItem";
export default function HelpMenuItem() { interface ContactMenuItem {
onHelpItemSelect: () => void;
}
export default function ContactMenuItem(props: ContactMenuItem) {
const { onHelpItemSelect } = props;
return ( return (
<> <>
<IntercomMenuItem /> <IntercomMenuItem onHelpItemSelect={onHelpItemSelect} />
<ZendeskMenuItem /> <ZendeskMenuItem onHelpItemSelect={onHelpItemSelect} />
<HelpscoutMenuItem /> <HelpscoutMenuItem onHelpItemSelect={onHelpItemSelect} />
<FreshChatMenuItem onHelpItemSelect={onHelpItemSelect} />
</> </>
); );
} }

View File

@ -7,6 +7,8 @@ import { trpc } from "@calcom/trpc/react";
import { Button, showToast } from "@calcom/ui"; import { Button, showToast } from "@calcom/ui";
import { FiExternalLink, FiAlertTriangle } from "@calcom/ui/components/icon"; import { FiExternalLink, FiAlertTriangle } from "@calcom/ui/components/icon";
import { useFreshChat } from "../lib/freshchat/FreshChatProvider";
import { isFreshChatEnabled } from "../lib/freshchat/FreshChatScript";
import ContactMenuItem from "./ContactMenuItem"; import ContactMenuItem from "./ContactMenuItem";
interface HelpMenuItemProps { interface HelpMenuItemProps {
@ -21,6 +23,8 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
const [, loadChat] = useChat(); const [, loadChat] = useChat();
const { t } = useLocale(); const { t } = useLocale();
const { setActive: setFreshChat } = useFreshChat();
const mutation = trpc.viewer.submitFeedback.useMutation({ const mutation = trpc.viewer.submitFeedback.useMutation({
onSuccess: () => { onSuccess: () => {
setDisableSubmit(true); setDisableSubmit(true);
@ -43,7 +47,6 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
<div className="w-full py-5"> <div className="w-full py-5">
<p className="mb-1 px-5 text-gray-500">{t("resources").toUpperCase()}</p> <p className="mb-1 px-5 text-gray-500">{t("resources").toUpperCase()}</p>
<a <a
onClick={() => onHelpItemSelect()}
href="https://docs.cal.com/" href="https://docs.cal.com/"
target="_blank" target="_blank"
className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900" className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900"
@ -57,7 +60,6 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
/> />
</a> </a>
<a <a
onClick={() => onHelpItemSelect()}
href="https://developer.cal.com/" href="https://developer.cal.com/"
target="_blank" target="_blank"
className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900" className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900"
@ -70,8 +72,8 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
)} )}
/> />
</a> </a>
<div onClick={() => onHelpItemSelect()}> <div>
<ContactMenuItem /> <ContactMenuItem onHelpItemSelect={onHelpItemSelect} />
</div> </div>
</div> </div>
@ -202,7 +204,11 @@ export default function HelpMenuItem({ onHelpItemSelect }: HelpMenuItemProps) {
className="font-medium underline hover:text-gray-700" className="font-medium underline hover:text-gray-700"
onClick={() => { onClick={() => {
setActive(true); setActive(true);
if (isFreshChatEnabled) {
setFreshChat(true);
} else {
loadChat({ open: true }); loadChat({ open: true });
}
onHelpItemSelect(); onHelpItemSelect();
}}> }}>
{t("contact_support")} {t("contact_support")}

View File

@ -0,0 +1,30 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useFreshChat } from "./FreshChatProvider";
import { isFreshChatEnabled } from "./FreshChatScript";
interface FreshChatMenuItemProps {
onHelpItemSelect: () => void;
}
export default function FreshChatMenuItem(props: FreshChatMenuItemProps) {
const { onHelpItemSelect } = props;
const { t } = useLocale();
const { setActive } = useFreshChat();
if (!isFreshChatEnabled) return null;
return (
<>
<button
onClick={() => {
setActive(true);
onHelpItemSelect();
}}
className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900">
{t("contact_support")}
</button>
</>
);
}

View File

@ -0,0 +1,25 @@
import type { ReactNode, Dispatch, SetStateAction } from "react";
import { createContext, useState, useContext } from "react";
import FreshChatScript from "./FreshChatScript";
type FreshChatContextType = { active: boolean; setActive: Dispatch<SetStateAction<boolean>> };
const FreshChatContext = createContext<FreshChatContextType>({ active: false, setActive: () => undefined });
interface FreshChatProviderProps {
children: ReactNode;
}
export const useFreshChat = () => useContext(FreshChatContext);
export default function FreshChatProvider(props: FreshChatProviderProps) {
const [active, setActive] = useState(false);
return (
<FreshChatContext.Provider value={{ active, setActive }}>
{props.children}
{active && <FreshChatScript />}
</FreshChatContext.Provider>
);
}

View File

@ -0,0 +1,40 @@
import Script from "next/script";
import { trpc } from "@calcom/trpc/react";
declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fcWidget: any;
}
}
// eslint-disable-next-line turbo/no-undeclared-env-vars
const host = process.env.NEXT_PUBLIC_FRESHCHAT_HOST;
// eslint-disable-next-line turbo/no-undeclared-env-vars
const token = process.env.NEXT_PUBLIC_FRESHCHAT_TOKEN;
export const isFreshChatEnabled = host !== "undefined" && token !== "undefined";
export default function FreshChatScript() {
const { data } = trpc.viewer.me.useQuery();
return (
<Script
id="fresh-chat-sdk"
src="https://wchat.freshchat.com/js/widget.js"
onLoad={() => {
window.fcWidget.init({
token,
host,
externalId: data?.id,
lastName: data?.name,
email: data?.email,
meta: {
username: data?.username,
},
open: true,
});
}}
/>
);
}

View File

@ -3,7 +3,12 @@ import { HelpScout, useChat } from "react-live-chat-loader";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
export default function HelpscoutMenuItem() { interface HelpscoutMenuItemProps {
onHelpItemSelect: () => void;
}
export default function HelpscoutMenuItem(props: HelpscoutMenuItemProps) {
const { onHelpItemSelect } = props;
const { t } = useLocale(); const { t } = useLocale();
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
@ -12,10 +17,12 @@ export default function HelpscoutMenuItem() {
function handleClick() { function handleClick() {
setActive(true); setActive(true);
loadChat({ open: true }); loadChat({ open: true });
onHelpItemSelect();
} }
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (!process.env.NEXT_PUBLIC_HELPSCOUT_KEY) return null; if (!process.env.NEXT_PUBLIC_HELPSCOUT_KEY) return null;
else
return ( return (
<> <>
<button <button

View File

@ -2,16 +2,23 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useIntercom } from "./useIntercom"; import { useIntercom } from "./useIntercom";
export default function IntercomMenuItem() { interface IntercomMenuItemProps {
onHelpItemSelect: () => void;
}
export default function IntercomMenuItem(props: IntercomMenuItemProps) {
const { onHelpItemSelect } = props;
const { t } = useLocale(); const { t } = useLocale();
const { boot, show } = useIntercom(); const { boot, show } = useIntercom();
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (!process.env.NEXT_PUBLIC_INTERCOM_APP_ID) return null; if (!process.env.NEXT_PUBLIC_INTERCOM_APP_ID) return null;
else
return ( return (
<button <button
onClick={() => { onClick={() => {
boot(); boot();
show(); show();
onHelpItemSelect();
}} }}
className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900"> className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900">
{t("contact_support")} {t("contact_support")}

View File

@ -3,18 +3,27 @@ import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
// eslint-disable-next-line turbo/no-undeclared-env-vars
const ZENDESK_KEY = process.env.NEXT_PUBLIC_ZENDESK_KEY; const ZENDESK_KEY = process.env.NEXT_PUBLIC_ZENDESK_KEY;
export default function ZendeskMenuItem() { interface ZendeskMenuItemProps {
onHelpItemSelect: () => void;
}
export default function ZendeskMenuItem(props: ZendeskMenuItemProps) {
const { onHelpItemSelect } = props;
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
const { t } = useLocale(); const { t } = useLocale();
if (!process.env.NEXT_PUBLIC_ZENDESK_KEY) return null; if (!ZENDESK_KEY) return null;
else
return ( return (
<> <>
<button <button
onClick={() => setActive(true)} onClick={() => {
setActive(true);
onHelpItemSelect();
}}
className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900"> className="flex w-full px-5 py-2 pr-4 text-sm font-medium text-gray-700 hover:bg-gray-100 hover:text-gray-900">
{t("contact_support")} {t("contact_support")}
</button> </button>

View File

@ -63,6 +63,7 @@ import {
FiArrowLeft, FiArrowLeft,
} from "@calcom/ui/components/icon"; } from "@calcom/ui/components/icon";
import FreshChatProvider from "../ee/support/lib/freshchat/FreshChatProvider";
import { TeamInviteBadge } from "./TeamInviteBadge"; import { TeamInviteBadge } from "./TeamInviteBadge";
/* TODO: Migate this */ /* TODO: Migate this */
@ -316,6 +317,7 @@ function UserDropdown({ small }: { small?: boolean }) {
</div> </div>
<DropdownMenuPortal> <DropdownMenuPortal>
<FreshChatProvider>
<DropdownMenuContent <DropdownMenuContent
onInteractOutside={() => { onInteractOutside={() => {
setMenuOpen(false); setMenuOpen(false);
@ -399,7 +401,11 @@ function UserDropdown({ small }: { small?: boolean }) {
</DropdownItem> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="desktop-hidden hidden lg:flex"> <DropdownMenuItem className="desktop-hidden hidden lg:flex">
<DropdownItem StartIcon={FiDownload} target="_blank" rel="noreferrer" href={DESKTOP_APP_LINK}> <DropdownItem
StartIcon={FiDownload}
target="_blank"
rel="noreferrer"
href={DESKTOP_APP_LINK}>
{t("download_desktop_app")} {t("download_desktop_app")}
</DropdownItem> </DropdownItem>
</DropdownMenuItem> </DropdownMenuItem>
@ -416,6 +422,7 @@ function UserDropdown({ small }: { small?: boolean }) {
</> </>
)} )}
</DropdownMenuContent> </DropdownMenuContent>
</FreshChatProvider>
</DropdownMenuPortal> </DropdownMenuPortal>
</Dropdown> </Dropdown>
); );