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:
co-authored by
amit@cal.com <samit91848@gmail.com>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
cbc8d3d7a8
commit
9048d053ac
@@ -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);
|
||||
|
||||
|
||||
+69
-45
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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, () => {});
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+4
@@ -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,
|
||||
|
||||
+2
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user