diff --git a/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx b/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx index 01067670a1d..3ed6c53bf67 100644 --- a/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx +++ b/apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx @@ -13,6 +13,7 @@ import { Textarea, } from '@/components/emcn' import { Check } from '@/components/emcn/icons' +import { captureClientEvent } from '@/lib/posthog/client' import { DEMO_REQUEST_COMPANY_SIZE_OPTIONS, type DemoRequestPayload, @@ -163,6 +164,9 @@ export function DemoRequestModal({ children, theme = 'dark' }: DemoRequestModalP } setSubmitSuccess(true) + captureClientEvent('landing_demo_request_submitted', { + company_size: parsed.data.companySize, + }) } catch (error) { setSubmitError( error instanceof Error diff --git a/apps/sim/app/(landing)/components/footer/footer-cta.tsx b/apps/sim/app/(landing)/components/footer/footer-cta.tsx index c1c95a638da..f9af4ac4bcc 100644 --- a/apps/sim/app/(landing)/components/footer/footer-cta.tsx +++ b/apps/sim/app/(landing)/components/footer/footer-cta.tsx @@ -3,7 +3,9 @@ import { useCallback, useRef, useState } from 'react' import { ArrowUp } from 'lucide-react' import Link from 'next/link' +import { captureClientEvent } from '@/lib/posthog/client' import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel' +import { trackLandingCta } from '@/app/(landing)/landing-analytics' import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder' const MAX_HEIGHT = 120 @@ -21,6 +23,7 @@ export function FooterCTA() { const handleSubmit = useCallback(() => { if (isEmpty) return + captureClientEvent('landing_prompt_submitted', {}) landingSubmit(inputValue) }, [isEmpty, inputValue, landingSubmit]) @@ -94,12 +97,22 @@ export function FooterCTA() { target='_blank' rel='noopener noreferrer' className={`${CTA_BUTTON} border-[var(--landing-border-strong)] text-[var(--landing-text)] transition-colors hover:bg-[var(--landing-bg-elevated)]`} + onClick={() => + trackLandingCta({ + label: 'Docs', + section: 'footer_cta', + destination: 'https://docs.sim.ai', + }) + } > Docs + trackLandingCta({ label: 'Get started', section: 'footer_cta', destination: '/signup' }) + } > Get started diff --git a/apps/sim/app/(landing)/components/hero/hero.tsx b/apps/sim/app/(landing)/components/hero/hero.tsx index 775f241c337..7098c4abf71 100644 --- a/apps/sim/app/(landing)/components/hero/hero.tsx +++ b/apps/sim/app/(landing)/components/hero/hero.tsx @@ -3,6 +3,7 @@ import dynamic from 'next/dynamic' import Link from 'next/link' import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal' +import { trackLandingCta } from '@/app/(landing)/landing-analytics' const LandingPreview = dynamic( () => @@ -57,6 +58,9 @@ export default function Hero() { type='button' className={`${CTA_BASE} border-[var(--landing-border-strong)] bg-transparent text-[var(--landing-text)] transition-colors hover:bg-[var(--landing-bg-elevated)]`} aria-label='Get a demo' + onClick={() => + trackLandingCta({ label: 'Get a demo', section: 'hero', destination: 'demo_modal' }) + } > Get a demo @@ -65,6 +69,9 @@ export default function Hero() { href='/signup' className={`${CTA_BASE} gap-2 border-white bg-white text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]`} aria-label='Get started with Sim' + onClick={() => + trackLandingCta({ label: 'Get started', section: 'hero', destination: '/signup' }) + } > Get started diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx index 35cb85c1654..aea260a3cb0 100644 --- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx +++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx @@ -5,6 +5,7 @@ import { AnimatePresence, motion } from 'framer-motion' import { ArrowUp, Table } from 'lucide-react' import { Blimp, Checkbox, ChevronDown } from '@/components/emcn' import { TypeBoolean, TypeNumber, TypeText } from '@/components/emcn/icons' +import { captureClientEvent } from '@/lib/posthog/client' import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel' import { EASE_OUT } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data' import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder' @@ -151,6 +152,7 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({ const handleSubmit = useCallback(() => { if (isEmpty) return + captureClientEvent('landing_prompt_submitted', {}) landingSubmit(inputValue) }, [isEmpty, inputValue, landingSubmit]) diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx index 4ca06238463..ef5929963e7 100644 --- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx +++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx @@ -9,6 +9,7 @@ import { createPortal } from 'react-dom' import { Blimp, BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn' import { AgentIcon, HubspotIcon, OpenAIIcon, SalesforceIcon } from '@/components/icons' import { LandingPromptStorage } from '@/lib/core/utils/browser-storage' +import { captureClientEvent } from '@/lib/posthog/client' import { EASE_OUT, type EditorPromptData, @@ -147,6 +148,7 @@ export const LandingPreviewPanel = memo(function LandingPreviewPanel({ const handleSubmit = useCallback(() => { if (isEmpty) return + captureClientEvent('landing_prompt_submitted', {}) landingSubmit(inputValue) }, [isEmpty, inputValue, landingSubmit]) diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx index 0cc2a8c306a..8f595d69078 100644 --- a/apps/sim/app/(landing)/components/navbar/navbar.tsx +++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx @@ -13,6 +13,7 @@ import { } from '@/app/(landing)/components/navbar/components/blog-dropdown' import { DocsDropdown } from '@/app/(landing)/components/navbar/components/docs-dropdown' import { GitHubStars } from '@/app/(landing)/components/navbar/components/github-stars' +import { trackLandingCta } from '@/app/(landing)/landing-analytics' import { getBrandConfig } from '@/ee/whitelabeling' type DropdownId = 'docs' | 'blog' | null @@ -212,6 +213,13 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps href='/workspace' className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[var(--white)] bg-[var(--white)] px-[9px] text-[13.5px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]' aria-label='Go to app' + onClick={() => + trackLandingCta({ + label: 'Go to App', + section: 'navbar', + destination: '/workspace', + }) + } > Go to App @@ -221,6 +229,9 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps href='/login' className='inline-flex h-[30px] items-center rounded-[5px] border border-[var(--landing-border-strong)] px-[9px] text-[13.5px] text-[var(--landing-text)] transition-colors hover:bg-[var(--landing-bg-elevated)]' aria-label='Log in' + onClick={() => + trackLandingCta({ label: 'Log in', section: 'navbar', destination: '/login' }) + } > Log in @@ -228,6 +239,13 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps href='/signup' className='inline-flex h-[30px] items-center gap-[7px] rounded-[5px] border border-[var(--white)] bg-[var(--white)] px-2.5 text-[13.5px] text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]' aria-label='Get started with Sim' + onClick={() => + trackLandingCta({ + label: 'Get started', + section: 'navbar', + destination: '/signup', + }) + } > Get started @@ -303,7 +321,14 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps setMobileMenuOpen(false)} + onClick={() => { + trackLandingCta({ + label: 'Go to App', + section: 'navbar', + destination: '/workspace', + }) + setMobileMenuOpen(false) + }} aria-label='Go to app' > Go to App @@ -313,7 +338,10 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps setMobileMenuOpen(false)} + onClick={() => { + trackLandingCta({ label: 'Log in', section: 'navbar', destination: '/login' }) + setMobileMenuOpen(false) + }} aria-label='Log in' > Log in @@ -321,7 +349,14 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps setMobileMenuOpen(false)} + onClick={() => { + trackLandingCta({ + label: 'Get started', + section: 'navbar', + destination: '/signup', + }) + setMobileMenuOpen(false) + }} aria-label='Get started with Sim' > Get started diff --git a/apps/sim/app/(landing)/components/pricing/pricing.tsx b/apps/sim/app/(landing)/components/pricing/pricing.tsx index 455aea124c8..d4d0789467c 100644 --- a/apps/sim/app/(landing)/components/pricing/pricing.tsx +++ b/apps/sim/app/(landing)/components/pricing/pricing.tsx @@ -3,6 +3,7 @@ import Link from 'next/link' import { Badge } from '@/components/emcn' import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal' +import { trackLandingCta } from '@/app/(landing)/landing-analytics' interface PricingTier { id: string @@ -150,6 +151,13 @@ function PricingCard({ tier }: PricingCardProps) { @@ -158,6 +166,13 @@ function PricingCard({ tier }: PricingCardProps) { + trackLandingCta({ + label: tier.cta.label, + section: 'pricing', + destination: tier.cta.href || '/signup', + }) + } > {tier.cta.label} @@ -165,6 +180,13 @@ function PricingCard({ tier }: PricingCardProps) { + trackLandingCta({ + label: tier.cta.label, + section: 'pricing', + destination: tier.cta.href || '/signup', + }) + } > {tier.cta.label} diff --git a/apps/sim/app/(landing)/landing-analytics.tsx b/apps/sim/app/(landing)/landing-analytics.tsx index 10be29e5edd..d79e5faaa52 100644 --- a/apps/sim/app/(landing)/landing-analytics.tsx +++ b/apps/sim/app/(landing)/landing-analytics.tsx @@ -2,7 +2,8 @@ import { useEffect } from 'react' import { usePostHog } from 'posthog-js/react' -import { captureEvent } from '@/lib/posthog/client' +import { captureClientEvent, captureEvent } from '@/lib/posthog/client' +import type { PostHogEventMap } from '@/lib/posthog/events' export function LandingAnalytics() { const posthog = usePostHog() @@ -13,3 +14,11 @@ export function LandingAnalytics() { return null } + +/** + * Fire-and-forget tracker for landing page CTA clicks. + * Uses the non-hook client so it works in any click handler without requiring a PostHog provider ref. + */ +export function trackLandingCta(props: PostHogEventMap['landing_cta_clicked']): void { + captureClientEvent('landing_cta_clicked', props) +} diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index c186d82cd47..8d8a1d194b2 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -14,6 +14,18 @@ export interface PostHogEventMap { landing_page_viewed: Record + landing_cta_clicked: { + label: string + section: 'hero' | 'navbar' | 'footer_cta' | 'pricing' + destination: string + } + + landing_demo_request_submitted: { + company_size: string + } + + landing_prompt_submitted: Record + signup_page_viewed: Record subscription_created: {