feat(landing): add PostHog tracking for CTA clicks, demo requests, and prompt submissions (#3994)

* feat(landing): add PostHog tracking for CTA clicks, demo requests, and prompt submissions

* lint

* fix(landing): correct import ordering per project conventions

* chore(landing): apply linter import sorting
This commit is contained in:
Waleed
2026-04-06 12:50:16 -07:00
committed by GitHub
parent 8b1d749f5c
commit cd5cee3033
9 changed files with 110 additions and 4 deletions

View File

@@ -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

View File

@@ -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
</a>
<Link
href='/signup'
className={`${CTA_BUTTON} gap-2 border-white bg-white text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]`}
onClick={() =>
trackLandingCta({ label: 'Get started', section: 'footer_cta', destination: '/signup' })
}
>
Get started
</Link>

View File

@@ -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
</button>
@@ -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
</Link>

View File

@@ -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])

View File

@@ -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])

View File

@@ -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
</Link>
@@ -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
</Link>
@@ -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
</Link>
@@ -303,7 +321,14 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
<Link
href='/workspace'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[var(--white)] bg-[var(--white)] text-[14px] text-black transition-colors active:bg-[#E0E0E0]'
onClick={() => 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
<Link
href='/login'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[var(--landing-border-strong)] text-[14px] text-[var(--landing-text)] transition-colors active:bg-[var(--landing-bg-elevated)]'
onClick={() => 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
<Link
href='/signup'
className='flex h-[32px] items-center justify-center rounded-[5px] border border-[var(--white)] bg-[var(--white)] text-[14px] text-black transition-colors active:bg-[#E0E0E0]'
onClick={() => setMobileMenuOpen(false)}
onClick={() => {
trackLandingCta({
label: 'Get started',
section: 'navbar',
destination: '/signup',
})
setMobileMenuOpen(false)
}}
aria-label='Get started with Sim'
>
Get started

View File

@@ -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) {
<button
type='button'
className='flex h-[32px] w-full items-center justify-center rounded-[5px] border border-[var(--landing-border-light)] bg-transparent px-2.5 font-[430] font-season text-[14px] text-[var(--landing-text-dark)] transition-colors hover:bg-[var(--landing-bg-hover)]'
onClick={() =>
trackLandingCta({
label: tier.cta.label,
section: 'pricing',
destination: 'demo_modal',
})
}
>
{tier.cta.label}
</button>
@@ -158,6 +166,13 @@ function PricingCard({ tier }: PricingCardProps) {
<Link
href={tier.cta.href || '/signup'}
className='flex h-[32px] w-full items-center justify-center rounded-[5px] border border-[#1D1D1D] bg-[#1D1D1D] px-2.5 font-[430] font-season text-[14px] text-white transition-colors hover:border-[var(--landing-border)] hover:bg-[var(--landing-bg-elevated)]'
onClick={() =>
trackLandingCta({
label: tier.cta.label,
section: 'pricing',
destination: tier.cta.href || '/signup',
})
}
>
{tier.cta.label}
</Link>
@@ -165,6 +180,13 @@ function PricingCard({ tier }: PricingCardProps) {
<Link
href={tier.cta.href || '/signup'}
className='flex h-[32px] w-full items-center justify-center rounded-[5px] border border-[var(--landing-border-light)] px-2.5 font-[430] font-season text-[14px] text-[var(--landing-text-dark)] transition-colors hover:bg-[var(--landing-bg-hover)]'
onClick={() =>
trackLandingCta({
label: tier.cta.label,
section: 'pricing',
destination: tier.cta.href || '/signup',
})
}
>
{tier.cta.label}
</Link>

View File

@@ -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)
}

View File

@@ -14,6 +14,18 @@ export interface PostHogEventMap {
landing_page_viewed: Record<string, never>
landing_cta_clicked: {
label: string
section: 'hero' | 'navbar' | 'footer_cta' | 'pricing'
destination: string
}
landing_demo_request_submitted: {
company_size: string
}
landing_prompt_submitted: Record<string, never>
signup_page_viewed: Record<string, never>
subscription_created: {