feat: adding posthog libs for node + js (#16862)

* adding posthog libs for node + js

* updating lock

* move to init posthog client - pageview component

* adding identify in shell

* removing test event captures

* add signout tracking to dropdown as it was missed in merge

---------

Co-authored-by: sean <sean@brydon.io>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
This commit is contained in:
abhi-slash-git
2024-11-04 11:46:25 -05:00
committed by GitHub
co-authored by sean Bailey Pumfleet
parent dd944bace1
commit ddfbe25903
20 changed files with 271 additions and 47 deletions
+5
View File
@@ -102,6 +102,11 @@ NEXT_PUBLIC_INTERCOM_APP_ID=
# Secret to enable Intercom Identity Verification
INTERCOM_SECRET=
# Posthog Config
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=
# Zendesk Config
NEXT_PUBLIC_ZENDESK_KEY=
+4 -1
View File
@@ -14,6 +14,7 @@ import type { ReactNode } from "react";
import { useEffect } from "react";
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";
@@ -299,7 +300,9 @@ const AppProviders = (props: PageWrapperProps) => {
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>{RemainingProviders}</DynamicIntercomProvider>
<DynamicIntercomProvider>
<DynamicPostHogProvider>{RemainingProviders}</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
);
};
+15 -1
View File
@@ -7,11 +7,13 @@ import type { SSRConfig } from "next-i18next";
import { appWithTranslation } from "next-i18next";
import { ThemeProvider } from "next-themes";
import type { AppProps as NextAppProps, AppProps as NextJsAppProps } from "next/app";
import dynamic from "next/dynamic";
import type { ParsedUrlQuery } from "querystring";
import type { PropsWithChildren, ReactNode } from "react";
import { useEffect } from "react";
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";
@@ -56,6 +58,13 @@ export type AppProps = Omit<
err?: Error;
};
const PostHogPageView = dynamic(
() => import("@calcom/features/ee/event-tracking/lib/posthog/web/PostHogPageView"),
{
ssr: false,
}
);
type AppPropsWithChildren = AppProps & {
children: ReactNode;
};
@@ -319,7 +328,12 @@ const AppProviders = (props: AppPropsWithChildren) => {
return (
<DynamicHelpscoutProvider>
<DynamicIntercomProvider>{RemainingProviders}</DynamicIntercomProvider>
<DynamicIntercomProvider>
<DynamicPostHogProvider>
<PostHogPageView />
{RemainingProviders}
</DynamicPostHogProvider>
</DynamicIntercomProvider>
</DynamicHelpscoutProvider>
);
};
+3 -3
View File
@@ -624,7 +624,7 @@ export default function Signup({
</div>
</div>
</div>
<div className="border-subtle lg:bg-subtle mx-auto mt-24 w-full max-w-2xl flex-col justify-between rounded-l-2xl pl-4 dark:bg-none lg:mt-0 lg:flex lg:max-w-full lg:border lg:py-12 lg:pl-12">
<div className="border-subtle lg:bg-subtle mx-auto mt-24 w-full max-w-2xl flex-col justify-between rounded-l-2xl pl-4 lg:mt-0 lg:flex lg:max-w-full lg:border lg:py-12 lg:pl-12 dark:bg-none">
{IS_CALCOM && (
<>
<div className="-mt-4 mb-6 mr-12 grid w-full grid-cols-3 gap-5 pr-4 sm:gap-3 lg:grid-cols-4">
@@ -675,7 +675,7 @@ export default function Signup({
</div>
</>
)}
<div className="border-default hidden rounded-bl-2xl rounded-br-none rounded-tl-2xl border border-r-0 border-dashed bg-black/[3%] dark:bg-white/5 lg:block lg:py-[6px] lg:pl-[6px]">
<div className="border-default hidden rounded-bl-2xl rounded-br-none rounded-tl-2xl border border-r-0 border-dashed bg-black/[3%] lg:block lg:py-[6px] lg:pl-[6px] dark:bg-white/5">
<img className="block dark:hidden" src="/mock-event-type-list.svg" alt="Cal.com Booking Page" />
<img
className="hidden dark:block"
@@ -686,7 +686,7 @@ export default function Signup({
<div className="mr-12 mt-8 hidden h-full w-full grid-cols-3 gap-4 overflow-hidden lg:grid">
{FEATURES.map((feature) => (
<>
<div className="max-w-52 mb-8 flex flex-col leading-none sm:mb-0">
<div className="mb-8 flex max-w-52 flex-col leading-none sm:mb-0">
<div className="text-emphasis items-center">
<Icon name={feature.icon} className="mb-1 h-4 w-4" />
<span className="text-sm font-medium">{t(feature.title)}</span>
+2
View File
@@ -109,6 +109,8 @@
"nodemailer": "^6.7.8",
"nuqs": "^1.20.0",
"otplib": "^12.0.1",
"posthog-js": "^1.164.1",
"posthog-node": "^4.2.0",
"qrcode": "^1.5.1",
"raw-body": "^2.5.1",
"react": "^18.2.0",
+26 -17
View File
@@ -10,6 +10,7 @@ import GoogleProvider from "next-auth/providers/google";
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg";
import postHogClient from "@calcom/features/ee/event-tracking/lib/posthog/postHogClient";
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
@@ -947,24 +948,32 @@ export const getOptions = ({
// this is a workaround – in the future once we move to use the Account model in the DB
// we should use NextAuth's isNewUser flag instead: https://next-auth.js.org/configuration/events#signin
const isNewUser = new Date(user.createdDate) > new Date(Date.now() - 10 * 60 * 1000);
if ((isENVDev || IS_CALCOM) && process.env.DUB_API_KEY && isNewUser) {
const clickId = getDubId();
// check if there's a clickId (dub_id) cookie set by @dub/analytics
if (clickId) {
// here we use waitUntil – meaning this code will run async to not block the main thread
waitUntil(
// if so, send a lead event to Dub
// @see https://d.to/conversions/next-auth
dub.track.lead({
clickId,
eventName: "Sign Up",
customerId: user.id.toString(),
customerName: user.name,
customerEmail: user.email,
customerAvatar: user.image,
})
);
if ((isENVDev || IS_CALCOM) && isNewUser) {
if (process.env.DUB_API_KEY) {
const clickId = getDubId();
// check if there's a clickId (dub_id) cookie set by @dub/analytics
if (clickId) {
// here we use waitUntil – meaning this code will run async to not block the main thread
waitUntil(
// if so, send a lead event to Dub
// @see https://d.to/conversions/next-auth
dub.track.lead({
clickId,
eventName: "Sign Up",
customerId: user.id.toString(),
customerName: user.name,
customerEmail: user.email,
customerAvatar: user.image,
})
);
}
}
postHogClient().capture(user.id.toString(), "Sign Up", {
email: user.email,
name: user.name,
username: user.username,
});
}
},
},
+2 -2
View File
@@ -275,7 +275,7 @@ const BookerComponent = ({
data-testid="booker-container"
className={classNames(
...getBookerSizeClassNames(layout, bookerState, hideEventTypeDetails),
`bg-default dark:bg-muted grid max-w-full items-start dark:[color-scheme:dark] sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row`,
`bg-default dark:bg-muted grid max-w-full items-start sm:transition-[width] sm:duration-300 sm:motion-reduce:transition-none md:flex-row dark:[color-scheme:dark]`,
// We remove border only when the content covers entire viewport. Because in embed, it can almost never be the case that it covers entire viewport, we show the border there
(layout === BookerLayouts.MONTH_VIEW || isEmbed) && "border-subtle rounded-md",
!isEmbed && "sm:transition-[width] sm:duration-300",
@@ -411,7 +411,7 @@ const BookerComponent = ({
layout === BookerLayouts.COLUMN_VIEW
}
className={classNames(
"border-subtle rtl:border-default flex h-full w-full flex-col overflow-x-auto px-5 py-3 pb-0 rtl:border-r ltr:md:border-l",
"border-subtle rtl:border-default flex h-full w-full flex-col overflow-x-auto px-5 py-3 pb-0 ltr:md:border-l rtl:border-r",
layout === BookerLayouts.MONTH_VIEW &&
"h-full overflow-hidden md:w-[var(--booker-timeslots-width)]",
layout !== BookerLayouts.MONTH_VIEW && "sticky top-0"
@@ -0,0 +1,40 @@
// eslint-disable-next-line no-restricted-imports
import { noop } from "lodash";
import { PostHog, type PostHog as PostHogType } from "posthog-node";
let postHog: {
capture: PostHogType["capture"];
identify: PostHogType["identify"];
};
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (process.env.NEXT_PUBLIC_POSTHOG_KEY) {
// eslint-disable-next-line turbo/no-undeclared-env-vars
postHog = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
// eslint-disable-next-line turbo/no-undeclared-env-vars
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
});
} else {
postHog = {
capture: noop,
identify: noop,
};
}
export function postHogClient() {
function capture(distinctId: string, event: string, properties?: Record<string, any>) {
postHog?.capture({ distinctId, event, properties });
}
function identify(distinctId: string, properties?: Record<string, any>) {
postHog?.identify({ distinctId, properties });
}
return {
capture,
identify,
};
}
export default postHogClient;
@@ -0,0 +1,25 @@
"use client";
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { useEffect } from "react";
function Provider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return;
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
autocapture: false,
persistence: "memory",
request_batching: false,
// Enable debug mode in development
loaded: (posthog) => {
if (process.env.NODE_ENV === "development") posthog.debug();
},
});
}, []);
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}
export default Provider;
@@ -0,0 +1,14 @@
import dynamic from "next/dynamic";
import { Fragment } from "react";
const initPostProvider = () => {
// eslint-disable-next-line turbo/no-undeclared-env-vars
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
return Fragment;
}
return dynamic(() => import("./provider"));
};
const DynamicPostHogProvider = initPostProvider();
export default DynamicPostHogProvider;
@@ -0,0 +1,66 @@
// eslint-disable-next-line no-restricted-imports
import { noop } from "lodash";
import { usePostHog as usePostHogLib } from "posthog-js/react";
import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useHasTeamPlan, useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
import { trpc } from "@calcom/trpc/react";
// eslint-disable-next-line turbo/no-undeclared-env-vars
export const isPostHogEnabled = z.string().min(1).safeParse(process.env.NEXT_PUBLIC_POSTHOG_KEY).success;
type PostHogHook = {
capture: (event: string, properties?: Record<string, any>) => void;
identify: (distinctId: string, properties?: Record<string, any>) => void;
reset: () => void;
};
const usePostHogHook: () => PostHogHook = isPostHogEnabled
? usePostHogLib
: () => ({
identify: noop,
capture: noop,
reset: noop,
});
const usePostHog = () => {
const posthog = usePostHogHook();
const { data } = trpc.viewer.me.useQuery();
const { hasPaidPlan } = useHasPaidPlan();
const { hasTeamPlan } = useHasTeamPlan();
const identify = async () => {
if (!data) return;
posthog.identify(String(data.id), {
distinctId: String(data.id),
...(data && data?.name && { name: data.name }),
...(data && data?.email && { email: data.email }),
created_at: String(dayjs(data?.createdDate).unix()),
//keys should be snake cased
user_name: data?.username,
link: `${WEBSITE_URL}/${data?.username}`,
identity_provider: data?.identityProvider,
timezone: data?.timeZone,
locale: data?.locale,
has_paid_plan: hasPaidPlan,
has_team_plan: hasTeamPlan,
metadata: data?.metadata,
completed_onboarding: data?.completedOnboarding,
sum_of_bookings: data?.sumOfBookings,
sum_of_calendars: data?.sumOfCalendars,
sum_of_teams: data?.sumOfTeams,
has_orgs_plan: !!data?.organizationId,
organization: data?.organization?.slug,
sum_of_event_types: data?.sumOfEventTypes,
sum_of_team_event_types: data?.sumOfTeamEventTypes,
is_premium: data?.isPremium,
});
};
return { identify, capture: posthog.capture, reset: posthog.reset };
};
export default usePostHog;
@@ -0,0 +1,25 @@
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";
export default function PostHogPageView() {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthog = usePostHog();
useEffect(() => {
// Track pageviews
if (pathname && posthog) {
let url = window.origin + pathname;
if (searchParams.toString()) {
url = `${url}?${searchParams.toString()}`;
}
posthog.capture("$pageview", {
$current_url: url,
});
}
}, [pathname, searchParams, posthog]);
return null;
}
@@ -14,6 +14,8 @@ import { Button, Dialog, DialogClose, DialogContent, DialogFooter, showToast } f
import CreateEventTypeForm from "./CreateEventTypeForm";
import usePostHog from "../../ee/event-tracking/lib/posthog/userPostHog";
// this describes the uniform data needed to create a new event type on Profile or Team
export interface EventTypeParent {
teamId: number | null | undefined; // if undefined, then it's a profile
@@ -60,6 +62,7 @@ export default function CreateEventTypeDialog({
membershipRole: MembershipRole | null | undefined;
}[];
}) {
const postHog = usePostHog();
const { t } = useLocale();
const router = useRouter();
const orgBranding = useOrgBranding();
@@ -137,6 +140,7 @@ export default function CreateEventTypeDialog({
form={form}
isManagedEventType={isManagedEventType}
handleSubmit={(values) => {
postHog.capture("Event Created Frontend");
createMutation.mutate(values);
}}
SubmitButton={SubmitButton}
+1 -1
View File
@@ -23,7 +23,7 @@ const ClearFilters = () => {
color="secondary"
target="_blank"
rel="noreferrer"
className="min-w-24 h-[38px] border-0"
className="h-[38px] min-w-24 border-0"
onClick={() => {
clearFilters();
}}>
@@ -228,9 +228,9 @@ const BackButtonInSidebar = ({ name }: { name: string }) => {
data-testid={`vertical-tab-${name}`}>
<Icon
name="arrow-left"
className="h-4 w-4 stroke-[2px] ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180 md:mt-0"
className="h-4 w-4 stroke-[2px] md:mt-0 ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180"
/>
<Skeleton title={name} as="p" className="max-w-36 min-h-4 truncate" loadingClassName="ms-3">
<Skeleton title={name} as="p" className="min-h-4 max-w-36 truncate" loadingClassName="ms-3">
{name}
</Skeleton>
</Link>
@@ -313,7 +313,7 @@ const TeamListCollapsible = () => {
{!team.parentId && (
<img
src={getPlaceholderAvatar(team.logoUrl, team.name)}
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] md:mt-0 ltr:mr-2 rtl:ml-2"
alt={team.name || "Team logo"}
/>
)}
@@ -463,7 +463,7 @@ const SettingsSidebarContainer = ({
{tab && tab.icon && (
<Icon
name={tab.icon}
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
className="text-subtle h-[16px] w-[16px] stroke-[2px] md:mt-0 ltr:mr-3 rtl:ml-3"
/>
)}
{!tab.icon && tab?.avatar && (
@@ -508,7 +508,7 @@ const SettingsSidebarContainer = ({
{tab && tab.icon && (
<Icon
name={tab.icon}
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
className="text-subtle h-[16px] w-[16px] stroke-[2px] md:mt-0 ltr:mr-3 rtl:ml-3"
/>
)}
<Skeleton
@@ -542,7 +542,7 @@ const SettingsSidebarContainer = ({
{tab && tab.icon && (
<Icon
name={tab.icon}
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
className="text-subtle h-[16px] w-[16px] stroke-[2px] md:mt-0 ltr:mr-3 rtl:ml-3"
/>
)}
<Skeleton
@@ -597,7 +597,7 @@ const SettingsSidebarContainer = ({
{!otherTeam.parentId && (
<img
src={getPlaceholderAvatar(otherTeam.logoUrl, otherTeam.name)}
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] md:mt-0 ltr:mr-2 rtl:ml-2"
alt={otherTeam.name || "Team logo"}
/>
)}
@@ -227,9 +227,9 @@ const BackButtonInSidebar = ({ name }: { name: string }) => {
data-testid={`vertical-tab-${name}`}>
<Icon
name="arrow-left"
className="h-4 w-4 stroke-[2px] ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180 md:mt-0"
className="h-4 w-4 stroke-[2px] md:mt-0 ltr:mr-[10px] rtl:ml-[10px] rtl:rotate-180"
/>
<Skeleton title={name} as="p" className="max-w-36 min-h-4 truncate" loadingClassName="ms-3">
<Skeleton title={name} as="p" className="min-h-4 max-w-36 truncate" loadingClassName="ms-3">
{name}
</Skeleton>
</Link>
@@ -310,7 +310,7 @@ const TeamListCollapsible = () => {
{!team.parentId && (
<img
src={getPlaceholderAvatar(team.logoUrl, team.name)}
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] md:mt-0 ltr:mr-2 rtl:ml-2"
alt={team.name || "Team logo"}
/>
)}
@@ -457,7 +457,7 @@ const SettingsSidebarContainer = ({
{tab && tab.icon && (
<Icon
name={tab.icon}
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
className="text-subtle h-[16px] w-[16px] stroke-[2px] md:mt-0 ltr:mr-3 rtl:ml-3"
/>
)}
{!tab.icon && tab?.avatar && (
@@ -502,7 +502,7 @@ const SettingsSidebarContainer = ({
{tab && tab.icon && (
<Icon
name={tab.icon}
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
className="text-subtle h-[16px] w-[16px] stroke-[2px] md:mt-0 ltr:mr-3 rtl:ml-3"
/>
)}
<Skeleton
@@ -536,7 +536,7 @@ const SettingsSidebarContainer = ({
{tab && tab.icon && (
<Icon
name={tab.icon}
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
className="text-subtle h-[16px] w-[16px] stroke-[2px] md:mt-0 ltr:mr-3 rtl:ml-3"
/>
)}
<Skeleton
@@ -591,7 +591,7 @@ const SettingsSidebarContainer = ({
{!otherTeam.parentId && (
<img
src={getPlaceholderAvatar(otherTeam.logoUrl, otherTeam.name)}
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] ltr:mr-2 rtl:ml-2 md:mt-0"
className="h-[16px] w-[16px] self-start rounded-full stroke-[2px] md:mt-0 ltr:mr-2 rtl:ml-2"
alt={otherTeam.name || "Team logo"}
/>
)}
+14 -7
View File
@@ -1,7 +1,7 @@
import { useSession } from "next-auth/react";
import { usePathname, useRouter } from "next/navigation";
import type { Dispatch, ReactElement, ReactNode, SetStateAction } from "react";
import React, { cloneElement, Fragment } from "react";
import React, { cloneElement, useEffect } from "react";
import { Toaster } from "react-hot-toast";
import { useRedirectToLoginIfUnauthenticated } from "@calcom/features/auth/lib/hooks/useRedirectToLoginIfUnauthenticated";
@@ -16,6 +16,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { ButtonState, useNotifications } from "@calcom/lib/hooks/useNotifications";
import { Button, ErrorBoundary, HeadSeo, SkeletonText } from "@calcom/ui";
import usePostHog from "../ee/event-tracking/lib/posthog/userPostHog";
import { SideBarContainer } from "./SideBar";
import { TopNavContainer } from "./TopNav";
import { BannerContainer } from "./banners/LayoutBanner";
@@ -26,13 +27,19 @@ import { useAppTheme } from "./useAppTheme";
const Layout = (props: LayoutProps) => {
const { banners, bannersHeight } = useBanners();
const pathname = usePathname();
const postHog = usePostHog();
const isFullPageWithoutSidebar = pathname?.startsWith("/apps/routing-forms/reporting/");
const pageTitle = typeof props.heading === "string" && !props.title ? props.heading : props.title;
useBootIntercom();
useFormbricks();
useEffect(() => {
if (!props.isPublic) {
postHog.identify();
}
}, [props.isPublic, postHog]);
return (
<>
{!props.withoutSeo && (
@@ -47,7 +54,7 @@ const Layout = (props: LayoutProps) => {
<TimezoneChangeDialog />
<div className="flex min-h-screen flex-col">
<div className="flex flex-col min-h-screen">
{banners && !props.isPlatformUser && !isFullPageWithoutSidebar && (
<BannerContainer banners={banners} />
)}
@@ -58,7 +65,7 @@ const Layout = (props: LayoutProps) => {
) : (
<SideBarContainer isPlatformUser={props.isPlatformUser} bannersHeight={bannersHeight} />
)}
<div className="flex w-0 flex-1 flex-col">
<div className="flex flex-col flex-1 w-0">
<MainContainer {...props} />
</div>
</div>
@@ -172,7 +179,7 @@ export function ShellMain(props: LayoutProps) {
{props.heading && (
<h3
className={classNames(
"font-cal max-w-28 sm:max-w-72 md:max-w-80 text-emphasis inline truncate text-lg font-semibold tracking-wide sm:text-xl md:block xl:max-w-full",
"font-cal text-emphasis max-w-28 sm:max-w-72 md:max-w-80 inline truncate text-lg font-semibold tracking-wide sm:text-xl md:block xl:max-w-full",
props.smallHeading ? "text-base" : "text-xl",
props.hideHeadingOnMobile && "hidden"
)}>
@@ -180,7 +187,7 @@ export function ShellMain(props: LayoutProps) {
</h3>
)}
{props.subtitle && (
<p className="text-default hidden text-sm md:block" data-testid="subtitle">
<p className="hidden text-sm text-default md:block" data-testid="subtitle">
{!isLocaleReady ? <SkeletonText invisible /> : props.subtitle}
</p>
)}
@@ -236,7 +243,7 @@ function MainContainer({
...props
}: LayoutProps) {
return (
<main className="bg-default relative z-0 flex-1 focus:outline-none">
<main className="relative z-0 flex-1 bg-default focus:outline-none">
{/* show top navigation for md and smaller (tablet and phones) */}
{TopNavContainerProp}
<div className="max-w-full px-2 py-4 lg:px-6">
@@ -21,6 +21,7 @@ import {
// TODO (Platform): we shouldnt be importing from web here
import { useGetUserAttributes } from "@calcom/web/components/settings/platform/hooks/useGetUserAttributes";
import usePostHog from "../../ee/event-tracking/lib/posthog/userPostHog";
import FreshChatProvider from "../../ee/support/lib/freshchat/FreshChatProvider";
interface UserDropdownProps {
@@ -31,6 +32,7 @@ export function UserDropdown({ small }: UserDropdownProps) {
const { t } = useLocale();
const { data: user } = useMeQuery();
const pathname = usePathname();
const posthog = usePostHog();
const isPlatformPages = pathname?.startsWith("/settings/platform");
useEffect(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -204,7 +206,11 @@ export function UserDropdown({ small }: UserDropdownProps) {
type="button"
StartIcon="log-out"
aria-hidden="true"
onClick={() => signOut({ callbackUrl: "/auth/logout" })}>
onClick={() => {
posthog.capture("sign_out");
posthog.reset();
signOut({ callbackUrl: "/auth/logout" });
}}>
{t("sign_out")}
</DropdownItem>
</DropdownMenuItem>
+2
View File
@@ -33,6 +33,8 @@ declare namespace NodeJS {
readonly PAYMENT_FEE_PERCENTAGE: number | undefined;
readonly PAYMENT_FEE_FIXED: number | undefined;
readonly NEXT_PUBLIC_INTERCOM_APP_ID: string | undefined;
readonly NEXT_PUBLIC_POSTHOG_KEY: string | undefined;
readonly NEXT_PUBLIC_POSTHOG_HOST: string | undefined;
readonly TANDEM_CLIENT_ID: string | undefined;
readonly TANDEM_CLIENT_SECRET: string | undefined;
readonly TANDEM_BASE_URL: string | undefined;
+2
View File
@@ -456,6 +456,8 @@
"NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED",
"NEXT_PUBLIC_API_V2_ROOT_URL",
"NEXT_PUBLIC_VAPID_PUBLIC_KEY",
"NEXT_PUBLIC_POSTHOG_KEY",
"NEXT_PUBLIC_POSTHOG_HOST",
"VAPID_PRIVATE_KEY",
"HUDDLE01_API_TOKEN"
]