mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user