* 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>
151 lines
5.7 KiB
TypeScript
151 lines
5.7 KiB
TypeScript
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
|
import type { Session } from "next-auth";
|
|
import { useSession } from "next-auth/react";
|
|
import { EventCollectionProvider } from "next-collect/client";
|
|
import { ThemeProvider } from "next-themes";
|
|
import type { AppProps as NextAppProps } from "next/app";
|
|
import type { ReadonlyURLSearchParams } from "next/navigation";
|
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
|
|
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 useIsBookingPage from "@lib/hooks/useIsBookingPage";
|
|
import useIsThemeSupported from "@lib/hooks/useIsThemeSupported";
|
|
import type { WithLocaleProps } from "@lib/withLocale";
|
|
|
|
import type { PageWrapperProps } from "@components/PageWrapperAppDir";
|
|
|
|
import { getThemeProviderProps } from "./getThemeProviderProps";
|
|
|
|
// Workaround for https://github.com/vercel/next.js/issues/8592
|
|
export type AppProps = Omit<
|
|
NextAppProps<
|
|
WithLocaleProps<{
|
|
nonce: string | undefined;
|
|
themeBasis?: string;
|
|
session: Session;
|
|
}>
|
|
>,
|
|
"Component"
|
|
> & {
|
|
Component: NextAppProps["Component"] & {
|
|
requiresLicense?: boolean;
|
|
isBookingPage?: boolean | ((arg: { router: NextAppProps["router"] }) => boolean);
|
|
PageWrapper?: (props: AppProps) => JSX.Element;
|
|
};
|
|
|
|
/** Will be defined only is there was an error */
|
|
err?: Error;
|
|
};
|
|
|
|
const getEmbedNamespace = (searchParams: ReadonlyURLSearchParams) => {
|
|
// Mostly embed query param should be available on server. Use that there.
|
|
// Use the most reliable detection on client
|
|
return typeof window !== "undefined" ? window.getEmbedNamespace() : searchParams.get("embed") ?? null;
|
|
};
|
|
|
|
type CalcomThemeProps = Readonly<{
|
|
isBookingPage: boolean;
|
|
nonce: string | undefined;
|
|
children: React.ReactNode;
|
|
isThemeSupported: boolean;
|
|
}>;
|
|
|
|
const CalcomThemeProvider = (props: CalcomThemeProps) => {
|
|
// Use namespace of embed to ensure same namespaced embed are displayed with same theme. This allows different embeds on the same website to be themed differently
|
|
// One such example is our Embeds Demo and Testing page at http://localhost:3100
|
|
// Having `getEmbedNamespace` defined on window before react initializes the app, ensures that embedNamespace is available on the first mount and can be used as part of storageKey
|
|
const searchParams = useSearchParams();
|
|
const embedNamespace = searchParams ? getEmbedNamespace(searchParams) : null;
|
|
const isEmbedMode = typeof embedNamespace === "string";
|
|
const pathname = usePathname();
|
|
const { key, ...themeProviderProps } = getThemeProviderProps({
|
|
props,
|
|
isEmbedMode,
|
|
embedNamespace,
|
|
pathname,
|
|
searchParams,
|
|
});
|
|
|
|
return (
|
|
<ThemeProvider key={key} {...themeProviderProps}>
|
|
{/* Embed Mode can be detected reliably only on client side here as there can be static generated pages as well which can't determine if it's embed mode at backend */}
|
|
{/* color-scheme makes background:transparent not work in iframe which is required by embed. */}
|
|
{typeof window !== "undefined" && !isEmbedMode && (
|
|
<style jsx global>
|
|
{`
|
|
.dark {
|
|
color-scheme: dark;
|
|
}
|
|
`}
|
|
</style>
|
|
)}
|
|
{props.children}
|
|
</ThemeProvider>
|
|
);
|
|
};
|
|
|
|
function FeatureFlagsProvider({ children }: { children: React.ReactNode }) {
|
|
const flags = useFlags();
|
|
return <FeatureProvider value={flags}>{children}</FeatureProvider>;
|
|
}
|
|
|
|
function useOrgBrandingValues() {
|
|
const session = useSession();
|
|
return session?.data?.user.org;
|
|
}
|
|
|
|
function OrgBrandProvider({ children }: { children: React.ReactNode }) {
|
|
const orgBrand = useOrgBrandingValues();
|
|
return <OrgBrandingProvider value={{ orgBrand }}>{children}</OrgBrandingProvider>;
|
|
}
|
|
|
|
const AppProviders = (props: PageWrapperProps) => {
|
|
// No need to have intercom on public pages - Good for Page Performance
|
|
const isBookingPage = useIsBookingPage();
|
|
const isThemeSupported = useIsThemeSupported();
|
|
|
|
const RemainingProviders = (
|
|
<>
|
|
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
|
|
<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
|
|
nonce={props.nonce}
|
|
isThemeSupported={isThemeSupported}
|
|
isBookingPage={props.isBookingPage || isBookingPage}>
|
|
<FeatureFlagsProvider>
|
|
{props.isBookingPage || isBookingPage ? (
|
|
<OrgBrandProvider>{props.children}</OrgBrandProvider>
|
|
) : (
|
|
<DynamicIntercomProvider>
|
|
<OrgBrandProvider>{props.children}</OrgBrandProvider>
|
|
</DynamicIntercomProvider>
|
|
)}
|
|
</FeatureFlagsProvider>
|
|
</CalcomThemeProvider>
|
|
</TooltipProvider>
|
|
</EventCollectionProvider>
|
|
</>
|
|
);
|
|
|
|
if (props.isBookingPage || isBookingPage) {
|
|
return RemainingProviders;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DynamicHelpscoutProvider>
|
|
<DynamicPostHogProvider>{RemainingProviders}</DynamicPostHogProvider>
|
|
</DynamicHelpscoutProvider>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default AppProviders;
|