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>
This commit is contained in:
co-authored by
Syed Ali Shahbaz
CarinaWolli
sean-brydon
sean-brydon
Peer Richelsen
Omar López
Replexica
Joe Au-Yeung
Calcom Bot
Udit Takkar
Benny Joo
Carina Wollendorfer
Vijay
Anik Dhabal Babu
Alex van Andel
parent
3994929a51
commit
225313e391
@@ -112,6 +112,8 @@ NEXT_PUBLIC_POSTHOG_HOST=
|
||||
PLAIN_API_KEY=
|
||||
PLAIN_API_URL=https://api.plain.com/v1
|
||||
PLAIN_HMAC_SECRET_KEY=
|
||||
PLAIN_CHAT_ID=
|
||||
PLAIN_CHAT_HMAC_SECRET_KEY=
|
||||
|
||||
# Zendesk Config
|
||||
NEXT_PUBLIC_ZENDESK_KEY=
|
||||
|
||||
@@ -26,6 +26,7 @@ export async function handler(request: Request) {
|
||||
|
||||
// HMAC verification
|
||||
const incomingSignature = headersList.get("plain-request-signature");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const expectedSignature = createHmac("sha-256", process.env.PLAIN_HMAC_SECRET_KEY!)
|
||||
.update(JSON.stringify(requestBody))
|
||||
.digest("hex");
|
||||
@@ -88,18 +89,8 @@ export async function handler(request: Request) {
|
||||
rowAsideContent: [
|
||||
{
|
||||
componentBadge: {
|
||||
badgeLabel:
|
||||
customer.emailVerified === undefined
|
||||
? "Unknown"
|
||||
: customer.emailVerified
|
||||
? "Yes"
|
||||
: "No",
|
||||
badgeColor:
|
||||
customer.emailVerified === undefined
|
||||
? "YELLOW"
|
||||
: customer.emailVerified
|
||||
? "GREEN"
|
||||
: "RED",
|
||||
badgeLabel: customer.emailVerified ? "Yes" : "No",
|
||||
badgeColor: customer.emailVerified ? "GREEN" : "RED",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -195,18 +186,8 @@ export async function handler(request: Request) {
|
||||
rowAsideContent: [
|
||||
{
|
||||
componentBadge: {
|
||||
badgeLabel:
|
||||
customer.twoFactorEnabled === undefined
|
||||
? "Unknown"
|
||||
: customer.twoFactorEnabled
|
||||
? "Yes"
|
||||
: "No",
|
||||
badgeColor:
|
||||
customer.twoFactorEnabled === undefined
|
||||
? "YELLOW"
|
||||
: customer.twoFactorEnabled
|
||||
? "GREEN"
|
||||
: "RED",
|
||||
badgeLabel: customer.twoFactorEnabled ? "Yes" : "No",
|
||||
badgeColor: customer.twoFactorEnabled ? "GREEN" : "RED",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { createHmac } from "crypto";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||
import { apiRouteMiddleware } from "@calcom/lib/server/apiRouteMiddleware";
|
||||
|
||||
const responseSchema = z.object({
|
||||
hash: z.string(),
|
||||
email: z.string().email(),
|
||||
shortName: z.string(),
|
||||
appId: z.string(),
|
||||
fullName: z.string(),
|
||||
chatAvatarUrl: z.string(),
|
||||
});
|
||||
|
||||
async function handler(request: Request) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const session = await getServerSession({ req: request as any });
|
||||
if (!session?.user?.email) {
|
||||
return new Response("Unauthorized - No session email found", { status: 401 });
|
||||
}
|
||||
|
||||
const secret = process.env.PLAIN_CHAT_HMAC_SECRET_KEY;
|
||||
if (!secret) {
|
||||
return new Response("Missing Plain Chat secret", { status: 500 });
|
||||
}
|
||||
|
||||
const hmac = createHmac("sha256", secret);
|
||||
hmac.update(session.user.email.toLowerCase().trim());
|
||||
const hash = hmac.digest("hex");
|
||||
|
||||
const shortName =
|
||||
(session.user.name?.split(" ")[0] || session.user.email).charAt(0).toUpperCase() +
|
||||
(session.user.name?.split(" ")[0] || session.user.email).slice(1) || "User";
|
||||
|
||||
const response = responseSchema.parse({
|
||||
hash,
|
||||
email: session.user.email || "user@example.com",
|
||||
shortName,
|
||||
appId: process.env.PLAIN_CHAT_ID,
|
||||
fullName: session.user.name || "User",
|
||||
chatAvatarUrl: session.user.avatarUrl || "",
|
||||
});
|
||||
|
||||
return NextResponse.json(response);
|
||||
}
|
||||
|
||||
export const POST = apiRouteMiddleware(handler);
|
||||
@@ -17,12 +17,12 @@ import CacheProvider from "react-inlinesvg/provider";
|
||||
import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic";
|
||||
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
|
||||
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
|
||||
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
|
||||
import { FeatureProvider } from "@calcom/features/flags/context/provider";
|
||||
import { useFlags } from "@calcom/features/flags/hooks";
|
||||
import { MetaProvider } from "@calcom/ui";
|
||||
|
||||
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
|
||||
import PlainChat from "@lib/plain/plainChat";
|
||||
import type { WithLocaleProps } from "@lib/withLocale";
|
||||
import type { WithNonceProps } from "@lib/withNonce";
|
||||
|
||||
@@ -265,9 +265,11 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
|
||||
const AppProviders = (props: PageWrapperProps) => {
|
||||
// No need to have intercom on public pages - Good for Page Performance
|
||||
const isBookingPage = useIsBookingPage();
|
||||
|
||||
const RemainingProviders = (
|
||||
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
|
||||
<SessionProvider>
|
||||
<PlainChat />
|
||||
<CustomI18nextProvider i18n={props.i18n}>
|
||||
<TooltipProvider>
|
||||
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
|
||||
@@ -301,11 +303,11 @@ const AppProviders = (props: PageWrapperProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<DynamicHelpscoutProvider>
|
||||
<DynamicIntercomProvider>
|
||||
<>
|
||||
<DynamicHelpscoutProvider>
|
||||
<DynamicPostHogProvider>{Hydrated}</DynamicPostHogProvider>
|
||||
</DynamicIntercomProvider>
|
||||
</DynamicHelpscoutProvider>
|
||||
</DynamicHelpscoutProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ import CacheProvider from "react-inlinesvg/provider";
|
||||
import DynamicPostHogProvider from "@calcom/features/ee/event-tracking/lib/posthog/providerDynamic";
|
||||
import { OrgBrandingProvider } from "@calcom/features/ee/organizations/context/provider";
|
||||
import DynamicHelpscoutProvider from "@calcom/features/ee/support/lib/helpscout/providerDynamic";
|
||||
import DynamicIntercomProvider from "@calcom/features/ee/support/lib/intercom/providerDynamic";
|
||||
import { FeatureProvider } from "@calcom/features/flags/context/provider";
|
||||
import { useFlags } from "@calcom/features/flags/hooks";
|
||||
import { MetaProvider } from "@calcom/ui";
|
||||
|
||||
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
|
||||
import PlainChat from "@lib/plain/plainChat";
|
||||
import type { WithLocaleProps } from "@lib/withLocale";
|
||||
import type { WithNonceProps } from "@lib/withNonce";
|
||||
|
||||
@@ -282,7 +282,6 @@ function OrgBrandProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
const AppProviders = (props: AppPropsWithChildren) => {
|
||||
// No need to have intercom on public pages - Good for Page Performance
|
||||
const isBookingPage = useIsBookingPage();
|
||||
const { pageProps, ...rest } = props;
|
||||
|
||||
@@ -298,9 +297,9 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
||||
const RemainingProviders = (
|
||||
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
|
||||
<SessionProvider session={pageProps.session ?? undefined}>
|
||||
<PlainChat />
|
||||
<CustomI18nextProvider {...propsWithoutNonce}>
|
||||
<TooltipProvider>
|
||||
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
|
||||
<CalcomThemeProvider
|
||||
themeBasis={props.pageProps.themeBasis}
|
||||
nonce={props.pageProps.nonce}
|
||||
@@ -327,14 +326,14 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<DynamicHelpscoutProvider>
|
||||
<DynamicIntercomProvider>
|
||||
<>
|
||||
<DynamicHelpscoutProvider>
|
||||
<DynamicPostHogProvider>
|
||||
<PostHogPageView />
|
||||
{RemainingProviders}
|
||||
</DynamicPostHogProvider>
|
||||
</DynamicIntercomProvider>
|
||||
</DynamicHelpscoutProvider>
|
||||
</DynamicHelpscoutProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
"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;
|
||||
@@ -594,7 +594,7 @@ const nextConfig = {
|
||||
value: orgHostPath,
|
||||
},
|
||||
],
|
||||
destination: "/event-types?openIntercom=true",
|
||||
destination: "/event-types?openPlain=true",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -55,8 +55,8 @@ export default (
|
||||
rowAsideContent: [
|
||||
{
|
||||
componentBadge: {
|
||||
badgeLabel: emailVerified === null ? "Unknown" : emailVerified ? "Yes" : "No",
|
||||
badgeColor: emailVerified === null ? "YELLOW" : emailVerified ? "GREEN" : "RED",
|
||||
badgeLabel: emailVerified === null ? "No" : emailVerified ? "Yes" : "No",
|
||||
badgeColor: emailVerified === null ? "RED" : emailVerified ? "GREEN" : "RED",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -152,8 +152,8 @@ export default (
|
||||
rowAsideContent: [
|
||||
{
|
||||
componentBadge: {
|
||||
badgeLabel: twoFactorEnabled === null ? "Unknown" : twoFactorEnabled ? "Yes" : "No",
|
||||
badgeColor: twoFactorEnabled === null ? "YELLOW" : twoFactorEnabled ? "GREEN" : "RED",
|
||||
badgeLabel: twoFactorEnabled === null ? "No" : twoFactorEnabled ? "Yes" : "No",
|
||||
badgeColor: twoFactorEnabled === null ? "RED" : twoFactorEnabled ? "GREEN" : "RED",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2216,8 +2216,8 @@
|
||||
"org_no_teams_yet": "This organization has no teams yet",
|
||||
"org_no_teams_yet_description": "If you are an administrator, be sure to create teams to be shown here.",
|
||||
"set_up": "Set up",
|
||||
"my_profile": "My Profile",
|
||||
"my_settings": "My Settings",
|
||||
"my_profile": "My profile",
|
||||
"my_settings": "My settings",
|
||||
"crm": "CRM",
|
||||
"messaging": "Messaging",
|
||||
"sender_id_info": "Name or number shown as the sender of an SMS (some countries do not allow alphanumeric sender IDs)",
|
||||
|
||||
@@ -2,9 +2,8 @@ import { signOut } from "next-auth/react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { JOIN_COMMUNITY, ROADMAP, DESKTOP_APP_LINK } from "@calcom/lib/constants";
|
||||
import { ROADMAP, DESKTOP_APP_LINK } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
|
||||
import {
|
||||
@@ -23,6 +22,16 @@ import { useGetUserAttributes } from "@calcom/web/components/settings/platform/h
|
||||
|
||||
import FreshChatProvider from "../../ee/support/lib/freshchat/FreshChatProvider";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Plain?: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
init: (config: any) => void;
|
||||
open: () => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface UserDropdownProps {
|
||||
small?: boolean;
|
||||
}
|
||||
@@ -44,11 +53,12 @@ export function UserDropdown({ small }: UserDropdownProps) {
|
||||
});
|
||||
});
|
||||
|
||||
const [helpOpen, setHelpOpen] = useState(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
const onHelpItemSelect = () => {
|
||||
setHelpOpen(false);
|
||||
const handleHelpClick = () => {
|
||||
if (window.Plain) {
|
||||
window.Plain.open();
|
||||
}
|
||||
setMenuOpen(false);
|
||||
};
|
||||
|
||||
@@ -108,110 +118,88 @@ export function UserDropdown({ small }: UserDropdownProps) {
|
||||
align="start"
|
||||
onInteractOutside={() => {
|
||||
setMenuOpen(false);
|
||||
setHelpOpen(false);
|
||||
}}
|
||||
className="group overflow-hidden rounded-md">
|
||||
{helpOpen ? (
|
||||
<HelpMenuItem onHelpItemSelect={() => onHelpItemSelect()} />
|
||||
) : (
|
||||
<>
|
||||
{!isPlatformPages && (
|
||||
<>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
CustomStartIcon={
|
||||
<Icon name="user" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
}
|
||||
href="/settings/my-account/profile">
|
||||
{t("my_profile")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
CustomStartIcon={
|
||||
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
}
|
||||
href="/settings/my-account/general">
|
||||
{t("my_settings")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
CustomStartIcon={
|
||||
<Icon name="moon" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
}
|
||||
href="/settings/my-account/out-of-office">
|
||||
{t("out_of_office")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
StartIcon="messages-square"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={JOIN_COMMUNITY}>
|
||||
{t("join_our_community")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem StartIcon="map" target="_blank" href={ROADMAP}>
|
||||
{t("visit_roadmap")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
StartIcon="circle-help"
|
||||
aria-hidden="true"
|
||||
onClick={() => setHelpOpen(true)}>
|
||||
{t("help")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
{!isPlatformPages && (
|
||||
<DropdownMenuItem className="todesktop:hidden hidden lg:flex">
|
||||
<>
|
||||
{!isPlatformPages && (
|
||||
<>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
StartIcon="download"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={DESKTOP_APP_LINK}>
|
||||
{t("download_desktop_app")}
|
||||
type="button"
|
||||
CustomStartIcon={
|
||||
<Icon name="user" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
}
|
||||
href="/settings/my-account/profile">
|
||||
{t("my_profile")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{!isPlatformPages && isPlatformUser && (
|
||||
<DropdownMenuItem className="todesktop:hidden hidden lg:flex">
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
StartIcon="blocks"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="/settings/platform">
|
||||
Platform
|
||||
type="button"
|
||||
CustomStartIcon={
|
||||
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
}
|
||||
href="/settings/my-account/general">
|
||||
{t("my_settings")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
CustomStartIcon={
|
||||
<Icon name="moon" className="text-default h-4 w-4" aria-hidden="true" />
|
||||
}
|
||||
href="/settings/my-account/out-of-office">
|
||||
{t("out_of_office")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
StartIcon="log-out"
|
||||
aria-hidden="true"
|
||||
onClick={() => {
|
||||
signOut({ callbackUrl: "/auth/logout" });
|
||||
}}>
|
||||
{t("sign_out")}
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem StartIcon="map" target="_blank" href={ROADMAP}>
|
||||
{t("visit_roadmap")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
StartIcon="circle-help"
|
||||
aria-hidden="true"
|
||||
onClick={handleHelpClick}>
|
||||
{t("help")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
{!isPlatformPages && (
|
||||
<DropdownMenuItem className="todesktop:hidden hidden lg:flex">
|
||||
<DropdownItem StartIcon="download" target="_blank" rel="noreferrer" href={DESKTOP_APP_LINK}>
|
||||
{t("download_desktop_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
|
||||
{!isPlatformPages && isPlatformUser && (
|
||||
<DropdownMenuItem className="todesktop:hidden hidden lg:flex">
|
||||
<DropdownItem StartIcon="blocks" target="_blank" rel="noreferrer" href="/settings/platform">
|
||||
Platform
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
StartIcon="log-out"
|
||||
aria-hidden="true"
|
||||
onClick={() => {
|
||||
signOut({ callbackUrl: "/auth/logout" });
|
||||
}}>
|
||||
{t("sign_out")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
</DropdownMenuContent>
|
||||
</FreshChatProvider>
|
||||
</DropdownMenuPortal>
|
||||
|
||||
+3
-1
@@ -117,7 +117,9 @@
|
||||
"MOTIFLAND_DOCS_PROJECT_ID",
|
||||
"PLAIN_HMAC_SECRET_KEY",
|
||||
"PLAIN_API_KEY",
|
||||
"PLAIN_API_URL"
|
||||
"PLAIN_API_URL",
|
||||
"PLAIN_CHAT_ID",
|
||||
"PLAIN_CHAT_HMAC_SECRET_KEY"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
|
||||
Reference in New Issue
Block a user