feat: posthog version upgrade and added trackings (#24401)

* posthog version upgrade and calai banner tracking

* disable posthog for EU

* bunch more posthog tracking

* Revert yarn.lock changes

* add posthog package yarn changes

* fix: add missing posthog import and fix lint warning in MemberInvitationModal

Co-Authored-By: amit@cal.com <samit91848@gmail.com>

* fix: type check

* cubic fixes

* refactor

* remove ui playground

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Amit Sharma
2025-11-28 12:43:37 +00:00
committed by GitHub
co-authored by amit@cal.com <samit91848@gmail.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent cbc8d3d7a8
commit 9048d053ac
42 changed files with 570 additions and 271 deletions
@@ -64,8 +64,8 @@ const Page = async ({ searchParams: _searchParams }: PageProps) => {
const organizationRepository = getOrganizationRepository();
const isOrgPrivate = organizationId
? await organizationRepository.checkIfPrivate({
orgId: organizationId,
})
orgId: organizationId,
})
: false;
const permissionService = new PermissionCheckService();
@@ -81,12 +81,7 @@ const Page = async ({ searchParams: _searchParams }: PageProps) => {
heading={t("availability")}
subtitle={t("configure_availability")}
CTA={
<AvailabilityCTA
toggleGroupOptions={[
{ value: "mine", label: t("my_availability") },
...(canViewTeamAvailability ? [{ value: "team", label: t("team_availability") }] : []),
]}
/>
<AvailabilityCTA canViewTeamAvailability={canViewTeamAvailability} />
}>
{searchParams?.type === "team" && canViewTeamAvailability ? (
<AvailabilitySliderTable isOrg={!!organizationId} />
@@ -9,16 +9,12 @@ import { AvailabilityCTA } from "~/availability/availability-view";
export default function AvailabilityLoader() {
const { t } = useLocale();
const toggleGroupOptions = [
{ value: "mine", label: t("my_availability") },
{ value: "team", label: t("team_availability") },
];
return (
<ShellMainAppDir
heading={t("availability")}
subtitle={t("configure_availability")}
CTA={<AvailabilityCTA toggleGroupOptions={toggleGroupOptions} />}>
CTA={<AvailabilityCTA canViewTeamAvailability />}>
<SkeletonLoader />
</ShellMainAppDir>
);
@@ -1,4 +1,5 @@
"use client";
import posthog from "posthog-js";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -13,6 +14,9 @@ export const TeamsCTA = () => {
StartIcon="plus"
size="sm"
type="button"
onClick={() => {
posthog.capture("add_team_button_clicked")
}}
href={`${WEBAPP_URL}/settings/teams/new?returnTo=${WEBAPP_URL}/teams`}>
{t("new")}
</Button>
@@ -3,6 +3,7 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";
import posthog from "posthog-js";
import { isFallbackRoute } from "@calcom/app-store/routing-forms/lib/isFallbackRoute";
import type { RoutingFormWithResponseCount } from "@calcom/app-store/routing-forms/types/types";
@@ -46,6 +47,7 @@ function NewFormButton({ setNewFormDialogState }: { setNewFormDialogState: SetNe
data-testid="new-routing-form"
createFunction={(teamId) => {
setNewFormDialogState({ action: "new", target: teamId ? String(teamId) : "" });
posthog.capture("new_routing_form_button_clicked", { teamId });
}}
withPermission={{
permission: "routingForm.create",
@@ -76,6 +78,7 @@ export default function RoutingForms({ appUrl }: { appUrl: string }) {
useEffect(() => {
hookForm.reset({});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filters = getTeamsFiltersFromQuery(routerQuery);
@@ -37,13 +37,13 @@ const getTabs = (orgBranding: OrganizationBranding | null) => {
href: "/settings/my-account",
icon: "user",
children: [
{ name: "profile", href: "/settings/my-account/profile" },
{ name: "general", href: "/settings/my-account/general" },
{ name: "calendars", href: "/settings/my-account/calendars" },
{ name: "conferencing", href: "/settings/my-account/conferencing" },
{ name: "appearance", href: "/settings/my-account/appearance" },
{ name: "out_of_office", href: "/settings/my-account/out-of-office" },
{ name: "push_notifications", href: "/settings/my-account/push-notifications" },
{ name: "profile", href: "/settings/my-account/profile", trackingMetadata: { section: "my_account", page: "profile" } },
{ name: "general", href: "/settings/my-account/general", trackingMetadata: { section: "my_account", page: "general" } },
{ name: "calendars", href: "/settings/my-account/calendars", trackingMetadata: { section: "my_account", page: "calendars" } },
{ name: "conferencing", href: "/settings/my-account/conferencing", trackingMetadata: { section: "my_account", page: "conferencing" } },
{ name: "appearance", href: "/settings/my-account/appearance", trackingMetadata: { section: "my_account", page: "appearance" } },
{ name: "out_of_office", href: "/settings/my-account/out-of-office", trackingMetadata: { section: "my_account", page: "out_of_office" } },
{ name: "push_notifications", href: "/settings/my-account/push-notifications", trackingMetadata: { section: "my_account", page: "push_notifications" } },
// TODO
// { name: "referrals", href: "/settings/my-account/referrals" },
],
@@ -53,16 +53,16 @@ const getTabs = (orgBranding: OrganizationBranding | null) => {
href: "/settings/security",
icon: "key",
children: [
{ name: "password", href: "/settings/security/password" },
{ name: "impersonation", href: "/settings/security/impersonation" },
{ name: "2fa_auth", href: "/settings/security/two-factor-auth" },
{ name: "password", href: "/settings/security/password", trackingMetadata: { section: "security", page: "password" } },
{ name: "impersonation", href: "/settings/security/impersonation", trackingMetadata: { section: "security", page: "impersonation" } },
{ name: "2fa_auth", href: "/settings/security/two-factor-auth", trackingMetadata: { section: "security", page: "2fa_auth" } },
],
},
{
name: "billing",
href: "/settings/billing",
icon: "credit-card",
children: [{ name: "manage_billing", href: "/settings/billing" }],
children: [{ name: "manage_billing", href: "/settings/billing", trackingMetadata: { section: "billing", page: "manage_billing" } }],
},
{
name: "developer",
@@ -70,9 +70,9 @@ const getTabs = (orgBranding: OrganizationBranding | null) => {
icon: "terminal",
children: [
//
{ name: "webhooks", href: "/settings/developer/webhooks" },
{ name: "api_keys", href: "/settings/developer/api-keys" },
{ name: "admin_api", href: "/settings/organizations/admin-api" },
{ name: "webhooks", href: "/settings/developer/webhooks", trackingMetadata: { section: "developer", page: "webhooks" } },
{ name: "api_keys", href: "/settings/developer/api-keys", trackingMetadata: { section: "developer", page: "api_keys" } },
{ name: "admin_api", href: "/settings/organizations/admin-api", trackingMetadata: { section: "developer", page: "admin_api" } },
// TODO: Add profile level for embeds
// { name: "embeds", href: "/v2/settings/developer/embeds" },
],
@@ -84,36 +84,43 @@ const getTabs = (orgBranding: OrganizationBranding | null) => {
{
name: "profile",
href: "/settings/organizations/profile",
trackingMetadata: { section: "organization", page: "profile" },
},
{
name: "general",
href: "/settings/organizations/general",
trackingMetadata: { section: "organization", page: "general" },
},
...(orgBranding
? [
{
name: "members",
href: `${WEBAPP_URL}/settings/organizations/${orgBranding.slug}/members`,
isExternalLink: true,
},
]
{
name: "members",
href: `${WEBAPP_URL}/settings/organizations/${orgBranding.slug}/members`,
isExternalLink: true,
trackingMetadata: { section: "organization", page: "members" },
},
]
: []),
{
name: "privacy_and_security",
href: "/settings/organizations/privacy",
trackingMetadata: { section: "organization", page: "privacy_and_security" },
},
{
name: "SSO",
href: "/settings/organizations/sso",
trackingMetadata: { section: "organization", page: "sso" },
},
{
name: "directory_sync",
href: "/settings/organizations/dsync",
trackingMetadata: { section: "organization", page: "directory_sync" },
},
{
name: "admin_api",
href: "https://cal.com/docs/enterprise-features/api/api-reference/bookings#admin-access",
isExternalLink: true,
trackingMetadata: { section: "organization", page: "admin_api" },
},
],
},
@@ -135,31 +142,31 @@ const getTabs = (orgBranding: OrganizationBranding | null) => {
icon: "lock",
children: [
//
{ name: "features", href: "/settings/admin/flags" },
{ name: "license", href: "/auth/setup?step=1" },
{ name: "impersonation", href: "/settings/admin/impersonation" },
{ name: "apps", href: "/settings/admin/apps/calendar" },
{ name: "users", href: "/settings/admin/users" },
{ name: "organizations", href: "/settings/admin/organizations" },
{ name: "lockedSMS", href: "/settings/admin/lockedSMS" },
{ name: "oAuth", href: "/settings/admin/oAuth" },
{ name: "Workspace Platforms", href: "/settings/admin/workspace-platforms" },
{ name: "Playground", href: "/settings/admin/playground" },
{ name: "features", href: "/settings/admin/flags", trackingMetadata: { section: "admin", page: "features" } },
{ name: "license", href: "/auth/setup?step=1", trackingMetadata: { section: "admin", page: "license" } },
{ name: "impersonation", href: "/settings/admin/impersonation", trackingMetadata: { section: "admin", page: "impersonation" } },
{ name: "apps", href: "/settings/admin/apps/calendar", trackingMetadata: { section: "admin", page: "apps" } },
{ name: "users", href: "/settings/admin/users", trackingMetadata: { section: "admin", page: "users" } },
{ name: "organizations", href: "/settings/admin/organizations", trackingMetadata: { section: "admin", page: "organizations" } },
{ name: "lockedSMS", href: "/settings/admin/lockedSMS", trackingMetadata: { section: "admin", page: "locked_sms" } },
{ name: "oAuth", href: "/settings/admin/oAuth", trackingMetadata: { section: "admin", page: "oauth" } },
{ name: "Workspace Platforms", href: "/settings/admin/workspace-platforms", trackingMetadata: { section: "admin", page: "workspace_platforms" } },
{ name: "Playground", href: "/settings/admin/playground", trackingMetadata: { section: "admin", page: "playground" } },
],
},
];
tabs.find((tab) => {
if (tab.name === "security" && !HOSTED_CAL_FEATURES) {
tab.children?.push({ name: "sso_configuration", href: "/settings/security/sso" });
tab.children?.push({ name: "sso_configuration", href: "/settings/security/sso", trackingMetadata: { section: "security", page: "sso_configuration" } });
// TODO: Enable dsync for self hosters
// tab.children?.push({ name: "directory_sync", href: "/settings/security/dsync" });
}
if (tab.name === "admin" && IS_CALCOM) {
tab.children?.push({ name: "create_org", href: "/settings/organizations/new" });
tab.children?.push({ name: "create_org", href: "/settings/organizations/new", trackingMetadata: { section: "admin", page: "create_org" } });
}
if (tab.name === "admin" && IS_CALCOM) {
tab.children?.push({ name: "create_license_key", href: "/settings/license-key/new" });
tab.children?.push({ name: "create_license_key", href: "/settings/license-key/new", trackingMetadata: { section: "admin", page: "create_license_key" } });
}
});
@@ -216,6 +223,7 @@ const useTabs = ({
newArray.splice(4, 0, {
name: "attributes",
href: "/settings/organizations/attributes",
trackingMetadata: { section: "organization", page: "attributes" },
});
}
@@ -224,6 +232,7 @@ const useTabs = ({
newArray.push({
name: "delegation_credential",
href: "/settings/organizations/delegation-credential",
trackingMetadata: { section: "organization", page: "delegation_credential" },
});
}
@@ -233,6 +242,7 @@ const useTabs = ({
newArray.push({
name: "roles_and_permissions",
href: "/settings/organizations/roles",
trackingMetadata: { section: "organization", page: "roles_and_permissions" },
});
}
@@ -240,6 +250,7 @@ const useTabs = ({
newArray.push({
name: "billing",
href: "/settings/organizations/billing",
trackingMetadata: { section: "organization", page: "billing" },
});
}
} else {
@@ -247,6 +258,7 @@ const useTabs = ({
newArray.push({
name: "billing",
href: "/settings/organizations/billing",
trackingMetadata: { section: "organization", page: "billing" },
});
}
// Show roles page (modal will appear if PBAC not enabled)
@@ -254,7 +266,8 @@ const useTabs = ({
newArray.push({
name: "roles",
href: "/settings/organizations/roles",
isBadged: true, // Show "New" badge
isBadged: true, // Show "New" badge,
trackingMetadata: { section: "organization", page: "roles" },
});
}
}
@@ -345,6 +358,7 @@ const TeamRolesNavItem = ({
<VerticalTabItem
name={t("roles_and_permissions")}
href={`/settings/teams/${team.id}/roles`}
trackingMetadata={{ section: "team", page: "roles_and_permissions", teamId: team.id }}
textClassNames="px-3 text-emphasis font-medium text-sm"
disableChevron
/>
@@ -420,9 +434,8 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
setTeamMenuState(newTeamMenuState);
}
}}
aria-label={`${team.name} ${
teamMenuState[index].teamMenuOpen ? t("collapse_menu") : t("expand_menu")
}`}>
aria-label={`${team.name} ${teamMenuState[index].teamMenuOpen ? t("collapse_menu") : t("expand_menu")
}`}>
<div className="me-3">
{teamMenuState[index].teamMenuOpen ? (
<Icon name="chevron-down" className="h-4 w-4" />
@@ -430,6 +443,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
<Icon name="chevron-right" className="h-4 w-4" />
)}
</div>
{ }
{!team.parentId && (
<Image
src={getPlaceholderAvatar(team.logoUrl, team.name)}
@@ -452,6 +466,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
<VerticalTabItem
name={t("profile")}
href={`/settings/teams/${team.id}/profile`}
trackingMetadata={{ section: "team", page: "profile", teamId: team.id }}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="px-2! me-5 h-7 w-auto"
disableChevron
@@ -460,6 +475,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
<VerticalTabItem
name={t("members")}
href={`/settings/teams/${team.id}/members`}
trackingMetadata={{ section: "team", page: "members", teamId: team.id }}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="px-2! me-5 h-7 w-auto"
disableChevron
@@ -470,9 +486,9 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore this exists wtf?
(team.isOrgAdmin && team.isOrgAdmin)) && (
<>
{/* TODO */}
{/* <VerticalTabItem
<>
{/* TODO */}
{/* <VerticalTabItem
name={t("general")}
href={`${WEBAPP_URL}/settings/my-account/appearance`}
textClassNames="px-3 text-emphasis font-medium text-sm"
@@ -482,6 +498,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("appearance")}
href={`/settings/teams/${team.id}/appearance`}
textClassNames="px-3 text-emphasis font-medium text-sm"
trackingMetadata={{ section: "team", page: "appearance", teamId: team.id }}
className="px-2! me-5 h-7 w-auto"
disableChevron
/>
@@ -492,6 +509,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("billing")}
href={`/settings/teams/${team.id}/billing`}
textClassNames="px-3 text-emphasis font-medium text-sm"
trackingMetadata={{ section: "team", page: "billing", teamId: team.id }}
className="px-2! me-5 h-7 w-auto"
disableChevron
/>
@@ -501,6 +519,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("settings")}
href={`/settings/teams/${team.id}/settings`}
textClassNames="px-3 text-emphasis font-medium text-sm"
trackingMetadata={{ section: "team", page: "settings", teamId: team.id }}
className="px-2! me-5 h-7 w-auto"
disableChevron
/>
@@ -601,6 +620,7 @@ const SettingsSidebarContainer = ({
className="text-subtle h-[16px] w-[16px] stroke-[2px] ltr:mr-3 rtl:ml-3 md:mt-0"
/>
)}
{ }
{!tab.icon && tab?.avatar && (
<Image
width={16}
@@ -626,6 +646,7 @@ const SettingsSidebarContainer = ({
name={t(child.name)}
isExternalLink={child.isExternalLink}
href={child.href || "/"}
trackingMetadata={child.trackingMetadata}
textClassNames="text-emphasis font-medium text-sm"
className={`px-2! h-7 w-fit ${
tab.children && index === tab.children?.length - 1 && "mb-3!"
@@ -668,6 +689,7 @@ const SettingsSidebarContainer = ({
<VerticalTabItem
name={t("add_a_team")}
href={`${WEBAPP_URL}/settings/teams/new`}
trackingMetadata={{ section: "team", page: "add_a_team" }}
textClassNames="px-3 items-center mt-2 text-emphasis font-medium text-sm"
className="px-2! me-5 h-7 w-auto"
icon="plus"
@@ -742,11 +764,10 @@ const SettingsSidebarContainer = ({
setOtherTeamMenuState(newOtherTeamMenuState);
}
}}
aria-label={`${otherTeam.name} ${
otherTeamMenuState[index].teamMenuOpen
? t("collapse_menu")
: t("expand_menu")
}`}>
aria-label={`${otherTeam.name} ${otherTeamMenuState[index].teamMenuOpen
? t("collapse_menu")
: t("expand_menu")
}`}>
<div className="me-3">
{otherTeamMenuState[index].teamMenuOpen ? (
<Icon name="chevron-down" className="h-4 w-4" />
@@ -754,6 +775,7 @@ const SettingsSidebarContainer = ({
<Icon name="chevron-right" className="h-4 w-4" />
)}
</div>
{ }
{!otherTeam.parentId && (
<Image
src={getPlaceholderAvatar(otherTeam.logoUrl, otherTeam.name)}
@@ -772,6 +794,7 @@ const SettingsSidebarContainer = ({
<VerticalTabItem
name={t("profile")}
href={`/settings/organizations/teams/other/${otherTeam.id}/profile`}
trackingMetadata={{ section: "other_team", page: "profile", teamId: otherTeam.id }}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="px-2! me-5 h-7 w-auto"
disableChevron
@@ -779,6 +802,7 @@ const SettingsSidebarContainer = ({
<VerticalTabItem
name={t("members")}
href={`/settings/organizations/teams/other/${otherTeam.id}/members`}
trackingMetadata={{ section: "other_team", page: "members", teamId: otherTeam.id }}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="px-2! me-5 h-7 w-auto"
disableChevron
+27
View File
@@ -0,0 +1,27 @@
"use client";
import { createContext, useContext } from "react";
interface GeoContextValue {
country: string;
}
const GeoContext = createContext<GeoContextValue | undefined>(undefined);
export function GeoProvider({
country,
children,
}: {
country: string;
children: React.ReactNode;
}) {
return <GeoContext.Provider value={{ country }}>{children}</GeoContext.Provider>;
}
export function useGeo() {
const context = useContext(GeoContext);
if (context === undefined) {
throw new Error("useGeo must be used within a GeoProvider");
}
return context;
}
+3 -1
View File
@@ -98,6 +98,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo
const h = await headers();
const nonce = h.get("x-csp-nonce") ?? "";
const country = h.get("cf-ipcountry") || h.get("x-vercel-ip-country") || "Unknown";
const { locale, direction, isEmbed, embedColorScheme } = await getInitialProps();
const ns = "common";
@@ -154,7 +156,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
]}
/>
<Providers isEmbed={isEmbed} nonce={nonce}>
<Providers isEmbed={isEmbed} nonce={nonce} country={country}>
<AppRouterI18nProvider translations={translations} locale={locale} ns={ns}>
{children}
</AppRouterI18nProvider>
+15 -10
View File
@@ -9,23 +9,28 @@ import { NotificationSoundHandler } from "@calcom/web/components/notification-so
import useIsBookingPage from "@lib/hooks/useIsBookingPage";
import { GeoProvider } from "./GeoContext";
type ProvidersProps = {
isEmbed: boolean;
children: React.ReactNode;
nonce: string | undefined;
country: string;
};
export function Providers({ isEmbed, children, nonce }: ProvidersProps) {
export function Providers({ isEmbed, children, country }: ProvidersProps) {
const isBookingPage = useIsBookingPage();
return (
<SessionProvider>
<TrpcProvider>
{!isEmbed && !isBookingPage && <NotificationSoundHandler />}
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
<CacheProvider>
<WebPushProvider>{children}</WebPushProvider>
</CacheProvider>
</TrpcProvider>
</SessionProvider>
<GeoProvider country={country}>
<SessionProvider>
<TrpcProvider>
{!isEmbed && !isBookingPage && <NotificationSoundHandler />}
{/* @ts-expect-error FIXME remove this comment when upgrading typescript to v5 */}
<CacheProvider>
<WebPushProvider>{children}</WebPushProvider>
</CacheProvider>
</TrpcProvider>
</SessionProvider>
</GeoProvider>
);
}
@@ -1,5 +1,6 @@
import Link from "next/link";
import { useState } from "react";
import posthog from "posthog-js";
import { InstallAppButtonWithoutPlanCheck } from "@calcom/app-store/InstallAppButtonWithoutPlanCheck";
import type { TDependencyData } from "@calcom/app-store/_appRegistry";
@@ -44,6 +45,7 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
return (
<div className="flex flex-row items-center justify-between p-5">
<div className="flex items-center space-x-3">
{/* eslint-disable @next/next/no-img-element */}
<img src={logo} alt={title} className="h-8 w-8" />
<p className="text-sm font-bold">{title}</p>
{isDefault && <Badge variant="green">{t("default")}</Badge>}
@@ -108,6 +110,12 @@ const AppConnectionItem = (props: IAppConnectionItem) => {
) : undefined
}
onClick={(event) => {
posthog.capture("onboarding_app_connect_clicked", {
app_title: title,
app_type: type,
app_slug: slug,
has_dependency: !!dependency,
});
// Save cookie key to return url step
document.cookie = `return-to=${window.location.href};path=/;max-age=3600;SameSite=Lax`;
buttonProps && buttonProps.onClick && buttonProps?.onClick(event);
@@ -3,6 +3,7 @@
import { useRouter } from "next/navigation";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import posthog from "posthog-js";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { md } from "@calcom/lib/markdownIt";
@@ -65,6 +66,8 @@ const UserProfile = ({ user }: UserProfileProps) => {
console.error(error);
}
posthog.capture("onboarding_completed");
await utils.viewer.me.get.refetch();
const redirectUrl = localStorage.getItem("onBoardingRedirect");
localStorage.removeItem("onBoardingRedirect");
+7 -1
View File
@@ -20,6 +20,8 @@ import type { WithLocaleProps } from "@lib/withLocale";
import type { PageWrapperProps } from "@components/PageWrapperAppDir";
import DynamicPostHogPageView from "@calcom/features/ee/event-tracking/lib/posthog/web/pageViewDynamic";
import { getThemeProviderProps } from "./getThemeProviderProps";
// Workaround for https://github.com/vercel/next.js/issues/8592
@@ -77,6 +79,7 @@ const CalcomThemeProvider = (props: CalcomThemeProps) => {
{/* 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 && (
//eslint-disable-next-line react/no-unknown-property
<style jsx global>
{`
.dark {
@@ -141,7 +144,10 @@ const AppProviders = (props: PageWrapperProps) => {
return (
<>
<DynamicHelpscoutProvider>
<DynamicPostHogProvider>{RemainingProviders}</DynamicPostHogProvider>
<DynamicPostHogProvider>
<DynamicPostHogPageView />
{RemainingProviders}
</DynamicPostHogProvider>
</DynamicHelpscoutProvider>
</>
);
@@ -3,6 +3,7 @@
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import posthog from "posthog-js";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -23,6 +24,9 @@ function VerifyEmailPage() {
useEffect(() => {
if (data?.isVerified) {
posthog.capture("verify_email_already_verified", {
onboarding_v3_enabled: flags["onboarding-v3"],
});
const gettingStartedPath = flags["onboarding-v3"] ? "/onboarding/getting-started" : "/getting-started";
router.replace(gettingStartedPath);
}
@@ -48,6 +52,7 @@ function VerifyEmailPage() {
className="underline"
loading={mutation.isPending}
onClick={() => {
posthog.capture("verify_email_resend_clicked");
showToast(t("send_email"), "success");
mutation.mutate();
}}>
@@ -5,6 +5,7 @@ import { revalidateAvailabilityList } from "app/(use-page-wrapper)/(main-nav)/av
import Link from "next/link";
import { useRouter, usePathname } from "next/navigation";
import { useCallback, useState } from "react";
import posthog from "posthog-js";
import { BulkEditDefaultForEventsModal } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
import type { BulkUpdatParams } from "@calcom/features/eventtypes/components/BulkEditDefaultForEventsModal";
@@ -182,15 +183,18 @@ export function AvailabilityList({ availabilities }: AvailabilityListProps) {
}
type AvailabilityCTAProps = {
toggleGroupOptions: {
value: string;
label: string;
}[];
canViewTeamAvailability: boolean;
};
export const AvailabilityCTA = ({ toggleGroupOptions }: AvailabilityCTAProps) => {
export const AvailabilityCTA = ({ canViewTeamAvailability }: AvailabilityCTAProps) => {
const searchParams = useCompatSearchParams();
const router = useRouter();
const pathname = usePathname();
const { t } = useLocale();
const toggleGroupOptions = [
{ value: "mine", label: t("my_availability") },
...(canViewTeamAvailability ? [{ value: "team", label: t("team_availability"), onClick: () => { posthog.capture("team_availability_toggle_clicked") } }] : []),
]
// Get a new searchParams string by merging the current
// searchParams with a provided key/value pair
@@ -6,6 +6,7 @@ import { usePathname, useRouter } from "next/navigation";
import { Suspense, useTransition } from "react";
import { Toaster } from "sonner";
import { z } from "zod";
import posthog from "posthog-js";
import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -40,33 +41,33 @@ const getStepsAndHeadersForUser = (t: TFunction) => {
subtitle: string[];
skipText?: string;
}[] = [
{
title: t("welcome_to_cal_header", { appName: APP_NAME }),
subtitle: [t("we_just_need_basic_info")],
},
{
title: t("connect_your_calendar"),
subtitle: [t("connect_your_calendar_instructions")],
skipText: t("connect_calendar_later"),
},
{
title: t("connect_your_video_app"),
subtitle: [t("connect_your_video_app_instructions")],
skipText: t("set_up_later"),
},
{
title: t("set_availability"),
subtitle: [
`${t("set_availability_getting_started_subtitle_1")} ${t(
"set_availability_getting_started_subtitle_2"
)}`,
],
},
{
title: t("nearly_there"),
subtitle: [t("nearly_there_instructions")],
},
];
{
title: t("welcome_to_cal_header", { appName: APP_NAME }),
subtitle: [t("we_just_need_basic_info")],
},
{
title: t("connect_your_calendar"),
subtitle: [t("connect_your_calendar_instructions")],
skipText: t("connect_calendar_later"),
},
{
title: t("connect_your_video_app"),
subtitle: [t("connect_your_video_app_instructions")],
skipText: t("set_up_later"),
},
{
title: t("set_availability"),
subtitle: [
`${t("set_availability_getting_started_subtitle_1")} ${t(
"set_availability_getting_started_subtitle_2"
)}`,
],
},
{
title: t("nearly_there"),
subtitle: [t("nearly_there_instructions")],
},
];
return {
steps: [...BASE_STEPS],
@@ -132,7 +133,13 @@ const OnboardingPage = (props: PageProps) => {
});
};
const goToNextStep = () => {
const goToNextStep = (wasSkipped: boolean = false) => {
posthog.capture("onboarding_step_completed", {
step: currentStep,
step_index: currentStepIndex,
from: from,
was_skipped: wasSkipped,
});
const nextIndex = currentStepIndex + 1;
const newStep = steps[nextIndex];
startTransition(() => {
@@ -195,7 +202,7 @@ const OnboardingPage = (props: PageProps) => {
data-testid="skip-step"
onClick={(event) => {
event.preventDefault();
goToNextStep();
goToNextStep(true);
}}
className="mt-8 cursor-pointer px-4 py-2 font-sans text-sm font-medium">
{headers[currentStepIndex]?.skipText}
@@ -207,7 +214,13 @@ const OnboardingPage = (props: PageProps) => {
<Button
color="minimal"
data-testid="sign-out"
onClick={() => signOut({ callbackUrl: "/auth/logout" })}
onClick={() => {
posthog.capture("onboarding_sign_out_clicked", {
step: currentStep,
step_index: currentStepIndex,
});
signOut({ callbackUrl: "/auth/logout" });
}}
className="mt-8 cursor-pointer px-4 py-2 font-sans text-sm font-medium">
{t("sign_out")}
</Button>
+64 -14
View File
@@ -12,6 +12,7 @@ import type { SubmitHandler } from "react-hook-form";
import { useForm, useFormContext } from "react-hook-form";
import { Toaster } from "sonner";
import { z } from "zod";
import posthog from "posthog-js";
import getStripe from "@calcom/app-store/stripepayment/lib/client";
import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
@@ -241,6 +242,15 @@ export default function Signup({
const signUp: SubmitHandler<FormValues> = async (_data) => {
const { cfToken, ...data } = _data;
posthog.capture("signup_form_submitted", {
has_token: !!token,
is_org_invite: isOrgInviteByLink,
org_slug: orgSlug,
is_premium_username: premiumUsername,
username_taken: usernameTaken,
});
await fetch("/api/auth/signup", {
body: JSON.stringify({
...data,
@@ -296,6 +306,13 @@ export default function Signup({
});
})
.catch((err) => {
posthog.capture("signup_form_submit_error", {
has_token: !!token,
is_org_invite: isOrgInviteByLink,
org_slug: orgSlug,
is_premium_username: premiumUsername,
error_message: err.message,
});
formMethods.setError("apiError", { message: err.message });
});
};
@@ -310,7 +327,7 @@ export default function Signup({
id="gtm-init-script"
// It is strictly not necessary to disable, but in a future update of react/no-danger this will error.
// And we don't want it to error here anyways
dangerouslySetInnerHTML={{
__html: `(function (w, d, s, l, i) {
w[l] = w[l] || []; w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
@@ -320,7 +337,7 @@ export default function Signup({
}}
/>
<noscript
dangerouslySetInnerHTML={{
__html: `<iframe src="https://www.googletagmanager.com/ns.html?id=${process.env.NEXT_PUBLIC_GTM_ID}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`,
}}
@@ -409,14 +426,14 @@ export default function Signup({
addOnLeading={
orgSlug
? truncateDomain(
`${getOrgFullOrigin(orgSlug, { protocol: true }).replace(
URL_PROTOCOL_REGEX,
""
)}/`
)
`${getOrgFullOrigin(orgSlug, { protocol: true }).replace(
URL_PROTOCOL_REGEX,
""
)}/`
)
: truncateDomain(
`${process.env.NEXT_PUBLIC_WEBSITE_URL.replace(URL_PROTOCOL_REGEX, "")}/`
)
`${process.env.NEXT_PUBLIC_WEBSITE_URL.replace(URL_PROTOCOL_REGEX, "")}/`
)
}
/>
) : null}
@@ -485,6 +502,13 @@ export default function Signup({
showToast("error", t("username_required"));
return;
}
posthog.capture("signup_saml_submit_button_clicked", {
has_token: !!token,
is_org_invite: isOrgInviteByLink,
org_slug: orgSlug,
});
// eslint-disable-next-line @calcom/eslint/avoid-web-storage
localStorage.setItem("username", username);
const sp = new URLSearchParams();
@@ -538,15 +562,24 @@ export default function Signup({
color="primary"
loading={isGoogleLoading}
CustomStartIcon={
<img
className={classNames("text-subtle mr-2 h-4 w-4", premiumUsername && "opacity-50")}
src="/google-icon-colored.svg"
alt="Continue with Google Icon"
/>
<>
{/* eslint-disable @next/next/no-img-element */}
<img
className={classNames("text-subtle mr-2 h-4 w-4", premiumUsername && "opacity-50")}
src="/google-icon-colored.svg"
alt="Continue with Google Icon"
/>
</>
}
className={classNames("w-full justify-center rounded-md text-center")}
data-testid="continue-with-google-button"
onClick={async () => {
posthog.capture("signup_google_button_clicked", {
has_token: !!token,
is_org_invite: isOrgInviteByLink,
org_slug: orgSlug,
has_prepopulated_username: !!prepopulateFormValues?.username,
});
setIsSamlSignup(false);
setIsGoogleLoading(true);
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL;
@@ -591,6 +624,11 @@ export default function Signup({
disabled={isGoogleLoading}
className={classNames("w-full justify-center rounded-md text-center")}
onClick={() => {
posthog.capture("signup_email_button_clicked", {
has_token: !!token,
is_org_invite: isOrgInviteByLink,
org_slug: orgSlug,
});
setDisplayEmailForm(true);
setIsSamlSignup(false);
}}
@@ -604,6 +642,11 @@ export default function Signup({
disabled={isGoogleLoading}
className={classNames("w-full justify-center rounded-md text-center")}
onClick={() => {
posthog.capture("signup_saml_button_clicked", {
has_token: !!token,
is_org_invite: isOrgInviteByLink,
org_slug: orgSlug,
});
setDisplayEmailForm(true);
setIsSamlSignup(true);
}}>
@@ -653,6 +696,7 @@ export default function Signup({
<>
<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">
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/product-cards/product-of-the-day.svg"
className="h-[34px] w-full dark:invert"
@@ -660,6 +704,7 @@ export default function Signup({
/>
</div>
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/product-cards/product-of-the-week.svg"
className="h-[34px] w-full dark:invert"
@@ -667,6 +712,7 @@ export default function Signup({
/>
</div>
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/product-cards/product-of-the-month.svg"
className="h-[34px] w-full dark:invert"
@@ -676,6 +722,7 @@ export default function Signup({
</div>
<div className="mb-6 mr-12 grid w-full grid-cols-3 gap-5 pr-4 sm:gap-3 lg:grid-cols-4">
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/product-cards/producthunt.svg"
className="h-[54px] w-full"
@@ -683,6 +730,7 @@ export default function Signup({
/>
</div>
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/product-cards/google-reviews.svg"
className="h-[54px] w-full"
@@ -690,6 +738,7 @@ export default function Signup({
/>
</div>
<div>
{/* eslint-disable @next/next/no-img-element */}
<img
src="/product-cards/g2.svg"
className="h-[54px] w-full"
@@ -701,6 +750,7 @@ export default function Signup({
)}
<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]">
<img className="block dark:hidden" src="/mock-event-type-list.svg" alt="Cal.com Booking Page" />
{/* eslint-disable @next/next/no-img-element */}
<img
className="hidden dark:block"
src="/mock-event-type-list-dark.svg"
+2 -2
View File
@@ -120,8 +120,8 @@
"nuqs": "^2.7.2",
"openid-client": "6.5.0",
"otplib": "^12.0.1",
"posthog-js": "^1.164.1",
"posthog-node": "^4.2.0",
"posthog-js": "^1.274.1",
"posthog-node": "^5.9.5",
"qrcode": "^1.5.1",
"raw-body": "^2.5.1",
"react": "^18.2.0",
+5 -5
View File
@@ -58,11 +58,11 @@ test.describe("Bookings", () => {
const firstUpcomingBooking = upcomingBookings.locator('[data-testid="booking-item"]').nth(0);
const secondUpcomingBooking = upcomingBookings.locator('[data-testid="booking-item"]').nth(1);
await expect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
firstUpcomingBooking.locator(`text=${bookingWhereFirstUserIsAttendee!.title}`)
).toBeVisible();
await expect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
secondUpcomingBooking.locator(`text=${bookingWhereFirstUserIsOrganizer!.title}`)
).toBeVisible();
});
@@ -375,19 +375,19 @@ test.describe("Bookings", () => {
//verify with the booking titles
const firstUpcomingBooking = bookingListItems.nth(0);
await expect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
firstUpcomingBooking.locator(`text=${thirdUserOrganizerBooking!.title}`)
).toBeVisible();
const secondUpcomingBooking = bookingListItems.nth(1);
await expect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
secondUpcomingBooking.locator(`text=${thirdUserAttendeeIndividualBooking!.title}`)
).toBeVisible();
const thirdUpcomingBooking = bookingListItems.nth(2);
await expect(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
thirdUpcomingBooking.locator(`text=${thirdUserAttendeeTeamEvent!.title}`)
).toBeVisible();
});
+10 -10
View File
@@ -25,7 +25,7 @@ test.afterEach(({ users }) => users.deleteAll());
test.describe("Reschedule Tests", async () => {
test("Should do a booking request reschedule from /bookings", async ({ page, users, bookings }) => {
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id!, {
status: BookingStatus.ACCEPTED,
});
@@ -57,7 +57,7 @@ test.describe("Reschedule Tests", async () => {
bookings,
}) => {
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id!, {
status: BookingStatus.ACCEPTED,
startTime: dayjs().subtract(2, "day").toDate(),
@@ -105,7 +105,7 @@ test.describe("Reschedule Tests", async () => {
test("Should display former time when rescheduling availability", async ({ page, users, bookings }) => {
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id!, {
status: BookingStatus.ACCEPTED,
rescheduled: true,
@@ -173,7 +173,7 @@ test.describe("Reschedule Tests", async () => {
const user = await users.create();
await user.apiLogin();
await user.installStripePersonal({ skip: true });
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const eventType = user.eventTypes.find((e) => e.slug === "paid")!;
const booking = await bookings.create(user.id, user.username, eventType.id, {
rescheduled: true,
@@ -218,7 +218,7 @@ test.describe("Reschedule Tests", async () => {
await user.apiLogin();
await user.installStripePersonal({ skip: true });
await users.logout();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const eventType = user.eventTypes.find((e) => e.slug === "paid")!;
const booking = await bookings.create(user.id, user.username, eventType.id, {
rescheduled: true,
@@ -238,7 +238,7 @@ test.describe("Reschedule Tests", async () => {
test("Opt in event should be PENDING when rescheduled by USER", async ({ page, users, bookings }) => {
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const eventType = user.eventTypes.find((e) => e.slug === "opt-in")!;
const booking = await bookings.create(user.id, user.username, eventType.id, {
status: BookingStatus.ACCEPTED,
@@ -259,7 +259,7 @@ test.describe("Reschedule Tests", async () => {
test("Opt in event should be ACCEPTED when rescheduled by OWNER", async ({ page, users, bookings }) => {
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const eventType = user.eventTypes.find((e) => e.slug === "opt-in")!;
const booking = await bookings.create(user.id, user.username, eventType.id, {
status: BookingStatus.ACCEPTED,
@@ -336,7 +336,7 @@ test.describe("Reschedule Tests", async () => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!process.env.DAILY_API_KEY, "DAILY_API_KEY is needed for this test");
const user = await users.create();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const eventType = user.eventTypes.find((e) => e.slug === "opt-in")!;
const confirmBooking = async (bookingId: number) => {
@@ -469,7 +469,7 @@ test.describe("Reschedule Tests", async () => {
});
const profileUsername = (await orgMember.getFirstProfile()).username;
const eventType = orgMember.eventTypes[0];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const orgSlug = org.slug!;
const booking = await bookings.create(orgMember.id, orgMember.username, eventType.id);
@@ -510,7 +510,7 @@ test.describe("Reschedule Tests", async () => {
roleInOrganization: MembershipRole.MEMBER,
});
const eventType = orgMember.eventTypes[0];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const orgSlug = org.slug!;
const booking = await bookings.create(orgMember.id, orgMember.username, eventType.id);
+25 -21
View File
@@ -1,5 +1,6 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import Link from "next/link";
import posthog from "posthog-js";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
@@ -15,7 +16,6 @@ import OmniInstallAppButton from "./OmniInstallAppButton";
export default function AppCard({
app,
description,
switchOnClick,
switchChecked,
children,
@@ -97,6 +97,10 @@ export default function AppCard({
size="sm"
disabled={!app.enabled || managedDisabled || disableSwitch}
onCheckedChange={(enabled) => {
posthog.capture("event_type_app_switch_toggled", {
app_slug: app.slug,
enabled: enabled,
});
if (switchOnClick) {
switchOnClick(enabled);
}
@@ -123,27 +127,27 @@ export default function AppCard({
{hideAppCardOptions
? null
: app?.isInstalled &&
switchChecked && (
<div ref={animationRef}>
{app.isSetupAlready === undefined || app.isSetupAlready ? (
<div className="relative text-sm [&_input]:mb-0 [&_input]:leading-4">
{!hideSettingsIcon && !isPlatform && (
<Link href={`/apps/${app.slug}/setup`} className="absolute right-4 top-4">
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
</Link>
)}
{children}
</div>
) : (
<div className="flex h-64 w-full flex-col items-center justify-center gap-4 ">
<p>{t("this_app_is_not_setup_already")}</p>
<Link href={`/apps/${app.slug}/setup`}>
<Button StartIcon="settings">{t("setup")}</Button>
switchChecked && (
<div ref={animationRef}>
{app.isSetupAlready === undefined || app.isSetupAlready ? (
<div className="relative text-sm [&_input]:mb-0 [&_input]:leading-4">
{!hideSettingsIcon && !isPlatform && (
<Link href={`/apps/${app.slug}/setup`} className="absolute right-4 top-4">
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
</Link>
</div>
)}
</div>
)}
)}
{children}
</div>
) : (
<div className="flex h-64 w-full flex-col items-center justify-center gap-4 ">
<p>{t("this_app_is_not_setup_already")}</p>
<Link href={`/apps/${app.slug}/setup`}>
<Button StartIcon="settings">{t("setup")}</Button>
</Link>
</div>
)}
</div>
)}
</Section>
);
}
@@ -24,7 +24,7 @@ import {
} from "./testUtils";
function todo(title: string) {
// eslint-disable-next-line playwright/no-skipped-test, @typescript-eslint/no-empty-function
// eslint-disable-next-line playwright/no-skipped-test
test.skip(title, () => {});
}
+83 -46
View File
@@ -2,6 +2,7 @@
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import posthog from "posthog-js";
import { InstallAppButton } from "@calcom/app-store/InstallAppButton";
import { isRedirectApp } from "@calcom/app-store/_utils/redirectApps";
@@ -58,6 +59,13 @@ export function AppCard({ app, credentials, searchText, userAdminTeams }: AppCar
}, [app.name, searchText]);
const handleAppInstall = () => {
posthog.capture("app_install_button_clicked", {
slug: app.slug,
app_type: app.type,
is_redirect: isRedirectApp(app.slug),
is_conferencing: isConferencing(app.categories || []),
});
if (isRedirectApp(app.slug)) {
// For redirect apps, open the external URL directly
if (app.url) window.open(app.url, "_blank", "noopener,noreferrer");
@@ -135,58 +143,87 @@ export function AppCard({ app, credentials, searchText, userAdminTeams }: AppCar
color="secondary"
className="flex w-32 grow justify-center"
href={`/apps/${app.slug}`}
onClick={() => { posthog.capture("app_card_details_clicked", { slug: app.slug }) }}
data-testid={`app-store-app-card-${app.slug}`}>
{t("details")}
</Button>
{app.isGlobal ||
(credentials && credentials.length > 0 && allowedMultipleInstalls) ||
(credentials && credentials.length > 0 && isRedirectApp(app.slug))
(credentials && credentials.length > 0 && allowedMultipleInstalls) ||
(credentials && credentials.length > 0 && isRedirectApp(app.slug))
? !app.isGlobal && (
<InstallAppButton
type={app.type}
teamsPlanRequired={app.teamsPlanRequired}
disableInstall={!!app.dependencies && !app.dependencyData?.some((data) => !data.installed)}
wrapperClassName="[@media(max-width:260px)]:w-full"
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
onClick: () => {
handleAppInstall();
},
loading: mutation.isPending,
};
}
return (
<InstallAppButtonChild paid={app.paid} isRedirect={isRedirectApp(app.slug)} {...props} />
);
}}
/>
)
<InstallAppButton
type={app.type}
teamsPlanRequired={app.teamsPlanRequired}
disableInstall={!!app.dependencies && !app.dependencyData?.some((data) => !data.installed)}
wrapperClassName="[@media(max-width:260px)]:w-full"
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
onClick: () => {
handleAppInstall();
},
loading: mutation.isPending,
};
} else {
const originalOnClick = props.onClick;
props = {
...props,
onClick: (e) => {
posthog.capture("app_install_button_clicked", {
slug: app.slug,
app_type: app.type,
is_redirect: isRedirectApp(app.slug),
is_conferencing: isConferencing(app.categories || []),
});
originalOnClick?.(e);
},
};
}
return (
<InstallAppButtonChild paid={app.paid} isRedirect={isRedirectApp(app.slug)} {...props} />
);
}}
/>
)
: credentials &&
!appInstalled && (
<InstallAppButton
type={app.type}
wrapperClassName="[@media(max-width:260px)]:w-full"
disableInstall={!!app.dependencies && app.dependencyData?.some((data) => !data.installed)}
teamsPlanRequired={app.teamsPlanRequired}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
disabled: !!props.disabled,
onClick: () => {
handleAppInstall();
},
loading: mutation.isPending,
};
}
return (
<InstallAppButtonChild paid={app.paid} isRedirect={isRedirectApp(app.slug)} {...props} />
);
}}
/>
)}
!appInstalled && (
<InstallAppButton
type={app.type}
wrapperClassName="[@media(max-width:260px)]:w-full"
disableInstall={!!app.dependencies && app.dependencyData?.some((data) => !data.installed)}
teamsPlanRequired={app.teamsPlanRequired}
render={({ useDefaultComponent, ...props }) => {
if (useDefaultComponent) {
props = {
...props,
disabled: !!props.disabled,
onClick: () => {
handleAppInstall();
},
loading: mutation.isPending,
};
} else {
const originalOnClick = props.onClick;
props = {
...props,
onClick: (e) => {
posthog.capture("app_install_button_clicked", {
slug: app.slug,
app_type: app.type,
is_redirect: isRedirectApp(app.slug),
is_conferencing: isConferencing(app.categories || []),
});
originalOnClick?.(e);
},
};
}
return (
<InstallAppButtonChild paid={app.paid} isRedirect={isRedirectApp(app.slug)} {...props} />
);
}}
/>
)}
</div>
<div className="max-w-44 absolute right-0 mr-4 flex flex-wrap justify-end gap-1">
{appAdded > 0 && !isRedirectApp(app.slug) ? (
@@ -1,6 +1,7 @@
import { useSession } from "next-auth/react";
import { useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import posthog from "posthog-js";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -171,6 +172,7 @@ export function SaveFilterSegmentButton() {
<Button
StartIcon="bookmark"
color="secondary"
onClick={() => posthog.capture("insights_routing_save_filter_clicked")}
disabled={!canSaveSegment}
data-testid="save-filter-segment-button">
{t("save")}
@@ -4,29 +4,35 @@ import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { useEffect, useRef } from "react";
import { useGeo } from "@calcom/web/app/GeoContext";
function Provider({ children }: { children: React.ReactNode }) {
const initializeOnce = useRef(false);
const { country } = useGeo();
useEffect(() => {
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY || initializeOnce.current) return;
initializeOnce.current = true;
const isUS = country === "US";
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://eu.i.posthog.com",
persistence: isUS ? "localStorage+cookie" : "memory",
autocapture: false,
person_profiles: "never",
persistence: "memory",
request_batching: true,
capture_pageview: false,
capture_pageleave: false,
cookieless_mode: isUS ? undefined : "always",
disable_session_recording: true,
advanced_disable_decide: true,
advanced_disable_flags: true,
loaded: (posthog) => {
if (process.env.NODE_ENV === "development") posthog.debug();
},
});
}, []);
}, [country]);
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}
@@ -4,11 +4,25 @@ import { usePathname, useSearchParams } from "next/navigation";
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";
const TRACKED_ROUTES = [
"/signup",
"/auth/verify-email",
"/getting-started",
"/onboarding/getting-started",
"/settings/teams/new",
"/settings/teams",
];
export default function PostHogPageView() {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthog = usePostHog();
useEffect(() => {
const shouldTrack = TRACKED_ROUTES.some(route => pathname?.startsWith(route));
if (!shouldTrack) return;
// Track pageviews
if (pathname && posthog) {
let url = window.origin + pathname;
@@ -0,0 +1,7 @@
import dynamic from "next/dynamic";
const DynamicPostHogPageView = dynamic(() => import("./PostHogPageView"), {
ssr: false,
});
export default DynamicPostHogPageView;
@@ -4,6 +4,7 @@ import { keepPreviousData } from "@tanstack/react-query";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState, useMemo } from "react";
import posthog from "posthog-js";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
@@ -160,6 +161,11 @@ export const AddNewTeamMembersForm = ({ teamId, isOrg }: { teamId: number; isOrg
className="w-full justify-center"
disabled={publishTeamMutation.isPending}
onClick={() => {
posthog.capture("onboard_members_continue_clicked", {
team_id: teamId,
is_org: isOrg,
members_count: totalFetched,
});
let uri = `/settings/teams/${teamId}/event-type`;
if (isOrg) {
uri = `/settings/organizations/${teamId}/add-teams`;
@@ -2,6 +2,7 @@
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import posthog from "posthog-js";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import slugify from "@calcom/lib/slugify";
@@ -85,6 +86,10 @@ export const CreateANewTeamForm = (props: CreateANewTeamFormProps) => {
form={newTeamFormMethods}
handleSubmit={(v) => {
if (!createTeamMutation.isPending) {
posthog.capture("create_team_checkout_clicked", {
team_name: v.name,
team_slug: v.slug,
});
setServerErrorMessage(null);
createTeamMutation.mutate(v);
}
@@ -2,6 +2,7 @@ import { useSession } from "next-auth/react";
import type { FormEvent } from "react";
import { useMemo, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import posthog from "posthog-js";
import TeamInviteFromOrg from "@calcom/ee/organizations/components/TeamInviteFromOrg";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
@@ -90,7 +91,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
);
const createInviteMutation = trpc.viewer.teams.createInvite.useMutation({
async onSuccess({ inviteLink }) {
async onSuccess() {
trpcContext.viewer.teams.get.invalidate();
trpcContext.viewer.teams.list.invalidate();
revalidateTeamsList();
@@ -235,8 +236,8 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
/>
</div>
<Form form={newMemberFormMethods} handleSubmit={(values) => props.onSubmit(values, resetFields)}>
<div className="mb-10 mt-6 stack-y-6">
<Form form={newMemberFormMethods} handleSubmit={(values) => { props.onSubmit(values, resetFields); posthog.capture("teams_modal_invite_members_button_clicked") }}>
<div className="mb-10 mt-6 space-y-6">
{/* Individual Invite */}
{modalImportMode === "INDIVIDUAL" && (
<Controller
@@ -388,7 +389,9 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
color="minimal"
className="me-2 ms-2"
onClick={() => {
props.onSettingsOpen && props.onSettingsOpen();
if (props.onSettingsOpen) {
props.onSettingsOpen();
}
newMemberFormMethods.reset();
}}
data-testid="edit-invite-link-button">
@@ -410,6 +413,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
// Credits to https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
if (typeof ClipboardItem !== "undefined") {
const inviteLinkClipbardItem = new ClipboardItem({
//eslint-disable-next-line no-async-promise-executor
"text/plain": new Promise(async (resolve) => {
// Instead of doing async work and then writing to clipboard, do async work in clipboard API itself
const { inviteLink } = await createInviteMutation.mutateAsync({
@@ -10,6 +10,7 @@ import {
} from "@tanstack/react-table";
import classNames from "classnames";
import { useSession } from "next-auth/react";
import posthog from "posthog-js";
import { signIn } from "next-auth/react";
import { usePathname } from "next/navigation";
import { useQueryState, parseAsBoolean } from "nuqs";
@@ -726,7 +727,7 @@ function MemberListContent(props: Props) {
type="button"
color="primary"
StartIcon="plus"
onClick={() => props.setShowMemberInvitationModal(true)}
onClick={() => { props.setShowMemberInvitationModal(true); posthog.capture("teams_add_new_members_button_clicked") }}
data-testid="new-member-button">
{t("add")}
</DataTableToolbar.CTA>
@@ -1,5 +1,6 @@
import { useRouter } from "next/navigation";
import { useState } from "react";
import posthog from "posthog-js";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
@@ -182,6 +183,7 @@ export function useWorkflowCreation() {
const openDialog = (teamId?: number) => {
setPendingTeamId(teamId);
setShowDialog(true);
posthog.capture("create_new_workflow_button_clicked", { teamId });
};
return {
@@ -1,3 +1,5 @@
import posthog from "posthog-js";
import { CAL_AI_PHONE_NUMBER_MONTHLY_PRICE } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -26,6 +28,7 @@ export function BuyNumberDialog({ open, onOpenChange, agentId, workflowId, teamI
window.location.href = data.checkoutUrl;
} else if (data.phoneNumber) {
showToast(t("phone_number_purchased_successfully"), "success");
posthog.capture("calai_phone_number_purchased");
await utils.viewer.me.get.invalidate();
onOpenChange(false);
if (agentId) {
@@ -116,6 +119,7 @@ export function BuyNumberDialog({ open, onOpenChange, agentId, workflowId, teamI
if (!agentId || !workflowId) {
return;
}
posthog.capture("calai_buy_number_button_clicked", { agentId, workflowId, teamId });
buyNumberMutation.mutate({
agentId: agentId,
workflowId: workflowId,
@@ -1,3 +1,4 @@
import posthog from "posthog-js";
import { useState } from "react";
import type { UseFormReturn } from "react-hook-form";
@@ -286,6 +287,7 @@ export function PhoneNumberTab({
<Button
onClick={() => {
if (readOnly) return;
posthog.capture("calai_buy_number_modal_opened");
setIsBuyDialogOpen(true);
}}
StartIcon="external-link"
@@ -3,6 +3,7 @@
import type { TFunction } from "i18next";
import { useQueryState } from "nuqs";
import { type ReactNode, useMemo, useRef, useState } from "react";
import posthog from "posthog-js";
import { DataTableSkeleton } from "@calcom/features/data-table";
import { downloadAsCsv } from "@calcom/lib/csvUtils";
@@ -43,6 +44,7 @@ function DownloadButton({ selectedPeriod, searchQuery }: DownloadButtonProps) {
e.preventDefault(); // Prevent default form submission
try {
posthog.capture("insights_routing_download_clicked", { teamId: routingParams.selectedTeamId });
const result = await utils.viewer.insights.routedToPerPeriodCsv.fetch({
...routingParams,
period: selectedPeriod,
@@ -1,4 +1,5 @@
import { useState } from "react";
import posthog from "posthog-js";
import dayjs from "@calcom/dayjs";
import { downloadAsCsv } from "@calcom/lib/csvUtils";
@@ -45,13 +46,14 @@ const Download = () => {
return result as PaginatedResponse;
}
return null;
} catch (error) {
} catch {
return null;
}
};
const handleDownloadClick = async () => {
try {
posthog.capture("insights_bookings_download_clicked", { teamId: insightsBookingParams.selectedTeamId });
setIsDownloading(true);
showProgressToast(0);
let allData: RawData[] = [];
@@ -82,7 +84,7 @@ const Download = () => {
)}.csv`;
downloadAsCsv(allData as Record<string, unknown>[], filename);
}
} catch (error) {
} catch {
showToast(t("unexpected_error_try_again"), "error");
} finally {
setIsDownloading(false);
@@ -1,6 +1,7 @@
import Link from "next/link";
import { usePathname, useSearchParams } from "next/navigation";
import React, { Fragment, useState, useEffect, useRef } from "react";
import posthog from "posthog-js";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
@@ -55,6 +56,13 @@ const useBuildHref = () => {
return buildHref;
};
const trackNavigationClick = (itemName: string, parentItemName?: string) => {
posthog.capture("navigation_item_clicked", {
item_name: itemName,
parent_name: parentItemName,
});
};
export type NavigationItemType = {
name: string;
href: string;
@@ -133,7 +141,10 @@ export const NavigationItem: React.FC<{
key={childItem.name}
href={buildHref(childItem)}
aria-current={childIsCurrent ? "page" : undefined}
onClick={() => setIsTooltipOpen(false)}
onClick={() => {
setIsTooltipOpen(false);
trackNavigationClick(childItem.name, item.name);
}}
className={classNames(
"group relative block rounded-md px-3 py-1 text-sm font-medium",
childIsCurrent
@@ -199,6 +210,7 @@ export const NavigationItem: React.FC<{
<Tooltip side="right" content={t(item.name)} className="lg:hidden">
<Link
data-test-id={item.name}
onClick={() => trackNavigationClick(item.name)}
href={isChild ? buildHref(item) : item.href}
aria-label={t(item.name)}
target={item.target}
@@ -208,9 +220,8 @@ export const NavigationItem: React.FC<{
? `aria-[aria-current='page']:bg-transparent!`
: `[&[aria-current='page']]:bg-emphasis`,
isChild
? `[&[aria-current='page']]:text-emphasis [&[aria-current='page']]:bg-emphasis hidden h-8 pl-16 lg:flex lg:pl-11 ${
props.index === 0 ? "mt-0" : "mt-px"
}`
? `[&[aria-current='page']]:text-emphasis [&[aria-current='page']]:bg-emphasis hidden h-8 pl-16 lg:flex lg:pl-11 ${props.index === 0 ? "mt-0" : "mt-px"
}`
: "[&[aria-current='page']]:text-emphasis mt-0.5 text-sm",
isLocaleReady
? "hover:bg-subtle todesktop:[&[aria-current='page']]:bg-emphasis todesktop:hover:bg-transparent hover:text-emphasis"
+20 -16
View File
@@ -1,4 +1,5 @@
import type { User as UserAuth } from "next-auth";
import posthog from "posthog-js";
import { useHasActiveTeamPlanAsOwner } from "@calcom/features/billing/hooks/useHasPaidPlan";
import { IS_DUB_REFERRALS_ENABLED } from "@calcom/lib/constants";
@@ -37,15 +38,15 @@ export function useBottomNavItems({
// Render above to prevent layout shift as much as possible
isTrial
? {
name: "skip_trial",
href: "",
isLoading: skipTeamTrialsMutation.isPending,
icon: "clock",
onClick: (e: { preventDefault: () => void }) => {
e.preventDefault();
skipTeamTrialsMutation.mutate({});
},
}
name: "skip_trial",
href: "",
isLoading: skipTeamTrialsMutation.isPending,
icon: "clock",
onClick: (e: { preventDefault: () => void }) => {
e.preventDefault();
skipTeamTrialsMutation.mutate({});
},
}
: null,
{
name: "view_public_page",
@@ -65,18 +66,21 @@ export function useBottomNavItems({
},
IS_DUB_REFERRALS_ENABLED
? {
name: "referral_text",
href: "/refer",
icon: "gift",
name: "referral_text",
href: "/refer",
icon: "gift",
onClick: () => {
posthog.capture("refer_and_earn_clicked")
}
}
: null,
isAdmin
? {
name: "impersonation",
href: "/settings/admin/impersonation",
icon: "lock",
}
name: "impersonation",
href: "/settings/admin/impersonation",
icon: "lock",
}
: null,
{
name: "settings",
+21 -2
View File
@@ -1,6 +1,7 @@
import shuffle from "lodash/shuffle";
import { useState, memo } from "react";
import posthog from "posthog-js";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { localStorage } from "@calcom/lib/webstorage";
@@ -226,6 +227,13 @@ function Tips() {
thumbnailUrl={tip.thumbnailUrl}
coverPhoto={tip.coverPhoto}
mediaLink={isTopTip ? tip.mediaLink : undefined}
mediaLinkOnClick={
isTopTip
? () => {
posthog.capture("tip_video_clicked", tip);
}
: undefined
}
title={t(tip.title)}
description={t(tip.description)}
learnMore={
@@ -233,11 +241,22 @@ function Tips() {
? {
href: tip.href,
text: t("learn_more"),
onClick: () => {
posthog.capture("tip_learn_more_clicked", tip);
},
}
: undefined
}
actionButton={
isTopTip ? { onClick: () => handleRemoveItem(tip.id), child: t("dismiss") } : undefined
isTopTip
? {
onClick: () => {
posthog.capture("tip_dismiss_clicked", tip);
handleRemoveItem(tip.id);
},
child: t("dismiss"),
}
: undefined
}
containerProps={{
tabIndex: isTopTip ? undefined : -1,
@@ -7,6 +7,7 @@ import { usePathname } from "next/navigation";
import { useQueryState, parseAsBoolean } from "nuqs";
import { useMemo, useReducer, useState } from "react";
import { createPortal } from "react-dom";
import posthog from "posthog-js";
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
import {
@@ -202,10 +203,10 @@ function UserListTableContent({
const filterType = isNumber
? ColumnFilterType.NUMBER
: isText
? ColumnFilterType.TEXT
: isSingleSelect
? ColumnFilterType.SINGLE_SELECT
: ColumnFilterType.MULTI_SELECT;
? ColumnFilterType.TEXT
: isSingleSelect
? ColumnFilterType.SINGLE_SELECT
: ColumnFilterType.MULTI_SELECT;
return {
id: attribute.id,
@@ -685,14 +686,15 @@ function UserListTableContent({
type="button"
color="primary"
StartIcon="plus"
onClick={() =>
onClick={() => {
dispatch({
type: "INVITE_MEMBER",
payload: {
showModal: true,
},
})
}
});
posthog.capture("add_organization_member_clicked")
}}
data-testid="new-organization-member-button">
{t("add")}
</DataTableToolbar.CTA>
+5 -1
View File
@@ -135,8 +135,10 @@ export interface BaseCardProps extends CVACardType {
learnMore?: {
href: string;
text: string;
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
};
mediaLink?: string;
mediaLinkOnClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
thumbnailUrl?: string;
structure?: string;
coverPhoto?: string;
@@ -157,6 +159,7 @@ export function Card({
learnMore,
coverPhoto,
buttonClassName,
mediaLinkOnClick,
}: BaseCardProps) {
const LinkComponent = learnMore && learnMore.href.startsWith("https") ? "a" : Link;
return (
@@ -200,7 +203,7 @@ export function Card({
</div>
{variant === "SidebarCard" && mediaLink && (
<a
onClick={actionButton?.onClick}
onClick={mediaLinkOnClick}
target="_blank"
rel="noreferrer noopener"
href={mediaLink}
@@ -253,6 +256,7 @@ export function Card({
href={learnMore.href}
target="_blank"
rel="noreferrer"
onClick={learnMore.onClick}
className={classNames("text-default text-xs font-medium", buttonClassName)}>
{learnMore.text}
</LinkComponent>
@@ -13,6 +13,7 @@ interface ToggleGroupProps extends Omit<RadixToggleGroup.ToggleGroupSingleProps,
tooltip?: string;
iconLeft?: ReactNode;
dataTestId?: string;
onClick?: VoidFunction;
}[];
isFullWidth?: boolean;
orientation?: "horizontal" | "vertical";
@@ -67,6 +68,7 @@ export const ToggleGroup = ({
<OptionalTooltipWrapper key={option.value} tooltipText={option.tooltip}>
<RadixToggleGroup.Item
disabled={option.disabled}
onClick={option?.onClick}
value={option.value}
data-testid={option.dataTestId ?? `toggle-group-item-${option.value}`}
className={classNames(
@@ -1,5 +1,6 @@
import Link from "next/link";
import { Fragment } from "react";
import posthog from "posthog-js";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useUrlMatchesCurrentUrl } from "@calcom/lib/hooks/useUrlMatchesCurrentUrl";
@@ -29,6 +30,8 @@ export type VerticalTabItemProps = {
isActive?: boolean;
isBadged?: boolean;
"data-testid"?: string;
//eslint-disable-next-line @typescript-eslint/no-explicit-any
trackingMetadata?: Record<string, any>;
};
const VerticalTabItem = ({
@@ -50,6 +53,9 @@ const VerticalTabItem = ({
<>
<Link
onClick={(e) => {
if (props.trackingMetadata) {
posthog.capture("settings_sidebar_button_clicked", props.trackingMetadata);
}
if (props.onClick) {
e.preventDefault();
props.onClick(name);
+34 -26
View File
@@ -4398,8 +4398,8 @@ __metadata:
openid-client: 6.5.0
otplib: ^12.0.1
postcss: ^8.4.18
posthog-js: ^1.164.1
posthog-node: ^4.2.0
posthog-js: ^1.274.1
posthog-node: ^5.9.5
qrcode: ^1.5.1
raw-body: ^2.5.1
react: ^18.2.0
@@ -11293,6 +11293,13 @@ __metadata:
languageName: node
linkType: hard
"@posthog/core@npm:1.2.4":
version: 1.2.4
resolution: "@posthog/core@npm:1.2.4"
checksum: e6d89de66338c69ea6b5c6c90cfbae23005bb4ce5c7fbf95e5d43ad73a382206f43c429a433c34d683904f750d7e210626c9d0d33ab4cfdf931241b3a61312eb
languageName: node
linkType: hard
"@prettier/sync@npm:^0.6.1":
version: 0.6.1
resolution: "@prettier/sync@npm:0.6.1"
@@ -20805,7 +20812,7 @@ __metadata:
languageName: node
linkType: hard
"axios@npm:^1.6.8, axios@npm:^1.7.4":
"axios@npm:^1.6.8":
version: 1.7.7
resolution: "axios@npm:1.7.7"
dependencies:
@@ -38838,25 +38845,24 @@ __metadata:
languageName: node
linkType: hard
"posthog-js@npm:^1.164.1":
version: 1.181.0
resolution: "posthog-js@npm:1.181.0"
"posthog-js@npm:^1.274.1":
version: 1.274.1
resolution: "posthog-js@npm:1.274.1"
dependencies:
"@posthog/core": 1.2.4
core-js: ^3.38.1
fflate: ^0.4.8
preact: ^10.19.3
web-vitals: ^4.2.0
checksum: e48b53844701c9db6c3c3da7af3f251e04e1572a1079c673e7302452c84e0f0e27c4b6b22ddb215afc1996a7f091b69218e927f83d7c0c18f7f7463fef06ee1a
languageName: node
linkType: hard
"posthog-node@npm:^4.2.0":
version: 4.2.1
resolution: "posthog-node@npm:4.2.1"
dependencies:
axios: ^1.7.4
rusha: ^0.8.14
checksum: f6871399069f329cd4d4c156e82a59c7ab222229d6f49fb62159738f0161a170d723b6707c229af2ca91ba3674f3bb243759d1a615333c23bec188b8122d35ab
web-vitals: ^4.2.4
peerDependencies:
"@rrweb/types": 2.0.0-alpha.17
rrweb-snapshot: 2.0.0-alpha.17
peerDependenciesMeta:
"@rrweb/types":
optional: true
rrweb-snapshot:
optional: true
checksum: 606d0843c7a7b30917fc89668bda7b6e731b688e1d9bd13b93b335030ad9dea862a0b0dcc806900b43bdb9ebc1fdc616d9f870ab7b0ab0d261da494fec5c85b3
languageName: node
linkType: hard
@@ -38869,6 +38875,15 @@ __metadata:
languageName: node
linkType: hard
"posthog-node@npm:^5.9.5":
version: 5.9.5
resolution: "posthog-node@npm:5.9.5"
dependencies:
"@posthog/core": 1.2.4
checksum: 5f7f316e49bb79c561c8ea8e7f084f9bf5bd07988948c5edd3ad5cb689e8f91417b34c6e6ee011ecb44edb6ce85a89128119ec4b71cf15647d524459f62b11bd
languageName: node
linkType: hard
"preact-render-to-string@npm:^5.1.19":
version: 5.2.6
resolution: "preact-render-to-string@npm:5.2.6"
@@ -41754,13 +41769,6 @@ __metadata:
languageName: node
linkType: hard
"rusha@npm:^0.8.14":
version: 0.8.14
resolution: "rusha@npm:0.8.14"
checksum: d76d7b4b5df1191712eca1e2465d07054dc9bd5e9ec357e1e0f3ddf95006a762330a3c0348e96ac39853656a3fbe02e65e780aa5f61b9355ad914fa296185623
languageName: node
linkType: hard
"rxjs@npm:*, rxjs@npm:^7.8.2":
version: 7.8.2
resolution: "rxjs@npm:7.8.2"
@@ -47153,7 +47161,7 @@ __metadata:
languageName: node
linkType: hard
"web-vitals@npm:^4.2.0":
"web-vitals@npm:^4.2.4":
version: 4.2.4
resolution: "web-vitals@npm:4.2.4"
checksum: 5b3ffe1db33f23aebf8cc8560ac574401a95939baafde5841835c1bb1c01f9a2478442f319f77aa0d7914739fc2f6b020c5d5b128c16c5c77ca6be2f9dfbbde6