Skip to content

Commit cd5cee3

Browse files
authored
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
1 parent 8b1d749 commit cd5cee3

File tree

9 files changed

+110
-4
lines changed

9 files changed

+110
-4
lines changed

apps/sim/app/(landing)/components/demo-request/demo-request-modal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Textarea,
1414
} from '@/components/emcn'
1515
import { Check } from '@/components/emcn/icons'
16+
import { captureClientEvent } from '@/lib/posthog/client'
1617
import {
1718
DEMO_REQUEST_COMPANY_SIZE_OPTIONS,
1819
type DemoRequestPayload,
@@ -163,6 +164,9 @@ export function DemoRequestModal({ children, theme = 'dark' }: DemoRequestModalP
163164
}
164165

165166
setSubmitSuccess(true)
167+
captureClientEvent('landing_demo_request_submitted', {
168+
company_size: parsed.data.companySize,
169+
})
166170
} catch (error) {
167171
setSubmitError(
168172
error instanceof Error

apps/sim/app/(landing)/components/footer/footer-cta.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import { useCallback, useRef, useState } from 'react'
44
import { ArrowUp } from 'lucide-react'
55
import Link from 'next/link'
6+
import { captureClientEvent } from '@/lib/posthog/client'
67
import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
8+
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
79
import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder'
810

911
const MAX_HEIGHT = 120
@@ -21,6 +23,7 @@ export function FooterCTA() {
2123

2224
const handleSubmit = useCallback(() => {
2325
if (isEmpty) return
26+
captureClientEvent('landing_prompt_submitted', {})
2427
landingSubmit(inputValue)
2528
}, [isEmpty, inputValue, landingSubmit])
2629

@@ -94,12 +97,22 @@ export function FooterCTA() {
9497
target='_blank'
9598
rel='noopener noreferrer'
9699
className={`${CTA_BUTTON} border-[var(--landing-border-strong)] text-[var(--landing-text)] transition-colors hover:bg-[var(--landing-bg-elevated)]`}
100+
onClick={() =>
101+
trackLandingCta({
102+
label: 'Docs',
103+
section: 'footer_cta',
104+
destination: 'https://docs.sim.ai',
105+
})
106+
}
97107
>
98108
Docs
99109
</a>
100110
<Link
101111
href='/signup'
102112
className={`${CTA_BUTTON} gap-2 border-white bg-white text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]`}
113+
onClick={() =>
114+
trackLandingCta({ label: 'Get started', section: 'footer_cta', destination: '/signup' })
115+
}
103116
>
104117
Get started
105118
</Link>

apps/sim/app/(landing)/components/hero/hero.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dynamic from 'next/dynamic'
44
import Link from 'next/link'
55
import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal'
6+
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
67

78
const LandingPreview = dynamic(
89
() =>
@@ -57,6 +58,9 @@ export default function Hero() {
5758
type='button'
5859
className={`${CTA_BASE} border-[var(--landing-border-strong)] bg-transparent text-[var(--landing-text)] transition-colors hover:bg-[var(--landing-bg-elevated)]`}
5960
aria-label='Get a demo'
61+
onClick={() =>
62+
trackLandingCta({ label: 'Get a demo', section: 'hero', destination: 'demo_modal' })
63+
}
6064
>
6165
Get a demo
6266
</button>
@@ -65,6 +69,9 @@ export default function Hero() {
6569
href='/signup'
6670
className={`${CTA_BASE} gap-2 border-white bg-white text-black transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]`}
6771
aria-label='Get started with Sim'
72+
onClick={() =>
73+
trackLandingCta({ label: 'Get started', section: 'hero', destination: '/signup' })
74+
}
6875
>
6976
Get started
7077
</Link>

apps/sim/app/(landing)/components/landing-preview/components/landing-preview-home/landing-preview-home.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AnimatePresence, motion } from 'framer-motion'
55
import { ArrowUp, Table } from 'lucide-react'
66
import { Blimp, Checkbox, ChevronDown } from '@/components/emcn'
77
import { TypeBoolean, TypeNumber, TypeText } from '@/components/emcn/icons'
8+
import { captureClientEvent } from '@/lib/posthog/client'
89
import { useLandingSubmit } from '@/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel'
910
import { EASE_OUT } from '@/app/(landing)/components/landing-preview/components/landing-preview-workflow/workflow-data'
1011
import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder'
@@ -151,6 +152,7 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({
151152

152153
const handleSubmit = useCallback(() => {
153154
if (isEmpty) return
155+
captureClientEvent('landing_prompt_submitted', {})
154156
landingSubmit(inputValue)
155157
}, [isEmpty, inputValue, landingSubmit])
156158

apps/sim/app/(landing)/components/landing-preview/components/landing-preview-panel/landing-preview-panel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { createPortal } from 'react-dom'
99
import { Blimp, BubbleChatPreview, ChevronDown, MoreHorizontal, Play } from '@/components/emcn'
1010
import { AgentIcon, HubspotIcon, OpenAIIcon, SalesforceIcon } from '@/components/icons'
1111
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
12+
import { captureClientEvent } from '@/lib/posthog/client'
1213
import {
1314
EASE_OUT,
1415
type EditorPromptData,
@@ -147,6 +148,7 @@ export const LandingPreviewPanel = memo(function LandingPreviewPanel({
147148

148149
const handleSubmit = useCallback(() => {
149150
if (isEmpty) return
151+
captureClientEvent('landing_prompt_submitted', {})
150152
landingSubmit(inputValue)
151153
}, [isEmpty, inputValue, landingSubmit])
152154

apps/sim/app/(landing)/components/navbar/navbar.tsx

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from '@/app/(landing)/components/navbar/components/blog-dropdown'
1414
import { DocsDropdown } from '@/app/(landing)/components/navbar/components/docs-dropdown'
1515
import { GitHubStars } from '@/app/(landing)/components/navbar/components/github-stars'
16+
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
1617
import { getBrandConfig } from '@/ee/whitelabeling'
1718

1819
type DropdownId = 'docs' | 'blog' | null
@@ -212,6 +213,13 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
212213
href='/workspace'
213214
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]'
214215
aria-label='Go to app'
216+
onClick={() =>
217+
trackLandingCta({
218+
label: 'Go to App',
219+
section: 'navbar',
220+
destination: '/workspace',
221+
})
222+
}
215223
>
216224
Go to App
217225
</Link>
@@ -221,13 +229,23 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
221229
href='/login'
222230
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)]'
223231
aria-label='Log in'
232+
onClick={() =>
233+
trackLandingCta({ label: 'Log in', section: 'navbar', destination: '/login' })
234+
}
224235
>
225236
Log in
226237
</Link>
227238
<Link
228239
href='/signup'
229240
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]'
230241
aria-label='Get started with Sim'
242+
onClick={() =>
243+
trackLandingCta({
244+
label: 'Get started',
245+
section: 'navbar',
246+
destination: '/signup',
247+
})
248+
}
231249
>
232250
Get started
233251
</Link>
@@ -303,7 +321,14 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
303321
<Link
304322
href='/workspace'
305323
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]'
306-
onClick={() => setMobileMenuOpen(false)}
324+
onClick={() => {
325+
trackLandingCta({
326+
label: 'Go to App',
327+
section: 'navbar',
328+
destination: '/workspace',
329+
})
330+
setMobileMenuOpen(false)
331+
}}
307332
aria-label='Go to app'
308333
>
309334
Go to App
@@ -313,15 +338,25 @@ export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps
313338
<Link
314339
href='/login'
315340
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)]'
316-
onClick={() => setMobileMenuOpen(false)}
341+
onClick={() => {
342+
trackLandingCta({ label: 'Log in', section: 'navbar', destination: '/login' })
343+
setMobileMenuOpen(false)
344+
}}
317345
aria-label='Log in'
318346
>
319347
Log in
320348
</Link>
321349
<Link
322350
href='/signup'
323351
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]'
324-
onClick={() => setMobileMenuOpen(false)}
352+
onClick={() => {
353+
trackLandingCta({
354+
label: 'Get started',
355+
section: 'navbar',
356+
destination: '/signup',
357+
})
358+
setMobileMenuOpen(false)
359+
}}
325360
aria-label='Get started with Sim'
326361
>
327362
Get started

apps/sim/app/(landing)/components/pricing/pricing.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import Link from 'next/link'
44
import { Badge } from '@/components/emcn'
55
import { DemoRequestModal } from '@/app/(landing)/components/demo-request/demo-request-modal'
6+
import { trackLandingCta } from '@/app/(landing)/landing-analytics'
67

78
interface PricingTier {
89
id: string
@@ -150,6 +151,13 @@ function PricingCard({ tier }: PricingCardProps) {
150151
<button
151152
type='button'
152153
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)]'
154+
onClick={() =>
155+
trackLandingCta({
156+
label: tier.cta.label,
157+
section: 'pricing',
158+
destination: 'demo_modal',
159+
})
160+
}
153161
>
154162
{tier.cta.label}
155163
</button>
@@ -158,13 +166,27 @@ function PricingCard({ tier }: PricingCardProps) {
158166
<Link
159167
href={tier.cta.href || '/signup'}
160168
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)]'
169+
onClick={() =>
170+
trackLandingCta({
171+
label: tier.cta.label,
172+
section: 'pricing',
173+
destination: tier.cta.href || '/signup',
174+
})
175+
}
161176
>
162177
{tier.cta.label}
163178
</Link>
164179
) : (
165180
<Link
166181
href={tier.cta.href || '/signup'}
167182
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)]'
183+
onClick={() =>
184+
trackLandingCta({
185+
label: tier.cta.label,
186+
section: 'pricing',
187+
destination: tier.cta.href || '/signup',
188+
})
189+
}
168190
>
169191
{tier.cta.label}
170192
</Link>

apps/sim/app/(landing)/landing-analytics.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import { useEffect } from 'react'
44
import { usePostHog } from 'posthog-js/react'
5-
import { captureEvent } from '@/lib/posthog/client'
5+
import { captureClientEvent, captureEvent } from '@/lib/posthog/client'
6+
import type { PostHogEventMap } from '@/lib/posthog/events'
67

78
export function LandingAnalytics() {
89
const posthog = usePostHog()
@@ -13,3 +14,11 @@ export function LandingAnalytics() {
1314

1415
return null
1516
}
17+
18+
/**
19+
* Fire-and-forget tracker for landing page CTA clicks.
20+
* Uses the non-hook client so it works in any click handler without requiring a PostHog provider ref.
21+
*/
22+
export function trackLandingCta(props: PostHogEventMap['landing_cta_clicked']): void {
23+
captureClientEvent('landing_cta_clicked', props)
24+
}

apps/sim/lib/posthog/events.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ export interface PostHogEventMap {
1414

1515
landing_page_viewed: Record<string, never>
1616

17+
landing_cta_clicked: {
18+
label: string
19+
section: 'hero' | 'navbar' | 'footer_cta' | 'pricing'
20+
destination: string
21+
}
22+
23+
landing_demo_request_submitted: {
24+
company_size: string
25+
}
26+
27+
landing_prompt_submitted: Record<string, never>
28+
1729
signup_page_viewed: Record<string, never>
1830

1931
subscription_created: {

0 commit comments

Comments
 (0)