Compare commits

...

2 Commits

Author SHA1 Message Date
GitHub Action
5f789e17d2 [automated] Apply ESLint and Oxfmt fixes 2026-06-18 00:27:51 +00:00
Deep Mehta
8fe6159497 feat(website): add explicit homepage CTA telemetry
Add a captureCtaClick helper to the website PostHog wrapper and fire
website:cta_clicked from the real CTA elements (hero, nav buttons, nav
products links, product cards) instead of relying on autocapture or
i18n text matching. Each event carries { button, location }.
2026-06-17 17:20:59 -07:00
7 changed files with 112 additions and 14 deletions

View File

@@ -1,17 +1,22 @@
<script setup lang="ts">
import { cn } from '@comfyorg/tailwind-utils'
import type { CtaButton } from '../../scripts/posthog'
import { captureCtaClick } from '../../scripts/posthog'
type NavDropdownItem = {
label: string
href: string
badge?: string
external?: boolean
ctaButton?: CtaButton
}
export type NavLink = {
label: string
href?: string
items?: NavDropdownItem[]
ctaButton?: CtaButton
}
const {
@@ -47,7 +52,7 @@ const emit = defineEmits<{
'group flex cursor-pointer items-center gap-1.5 py-3 text-sm font-bold tracking-wide uppercase transition-colors',
link.items.some((item) => currentPath === item.href)
? 'text-primary-comfy-yellow'
: 'text-primary-comfy-canvas hover:text-primary-warm-gray'
: 'hover:text-primary-warm-gray text-primary-comfy-canvas'
)
"
aria-haspopup="true"
@@ -62,7 +67,7 @@ const emit = defineEmits<{
'text-base leading-none transition-colors',
link.items.some((item) => currentPath === item.href)
? 'text-primary-comfy-yellow'
: 'text-primary-comfy-canvas group-hover:text-primary-warm-gray'
: 'group-hover:text-primary-warm-gray text-primary-comfy-canvas'
)
"
>
@@ -79,9 +84,10 @@ const emit = defineEmits<{
'flex items-center gap-1.5 py-3 text-sm font-bold tracking-wide uppercase transition-colors',
currentPath === link.href
? 'text-primary-comfy-yellow'
: 'text-primary-comfy-canvas hover:text-primary-warm-gray'
: 'hover:text-primary-warm-gray text-primary-comfy-canvas'
)
"
@click="link.ctaButton && captureCtaClick(link.ctaButton, 'nav')"
>
{{ link.label }}
</a>
@@ -102,15 +108,18 @@ const emit = defineEmits<{
'flex items-center gap-2 rounded-sm p-2 text-xs font-medium tracking-wide transition-colors',
currentPath === item.href
? 'text-primary-comfy-yellow'
: 'text-primary-comfy-canvas hover:bg-transparency-white-t4 hover:text-white'
: 'hover:bg-transparency-white-t4 text-primary-comfy-canvas hover:text-white'
)
"
@click="emit('close')"
@click="
(item.ctaButton && captureCtaClick(item.ctaButton, 'nav'),
emit('close'))
"
>
{{ item.label }}
<span
v-if="item.badge"
class="bg-primary-comfy-yellow font-formula-narrow text-primary-comfy-ink -skew-x-12 rounded-sm px-1 py-0.5 text-[9px]/3 leading-none font-bold"
class="bg-primary-comfy-yellow font-formula-narrow -skew-x-12 rounded-sm px-1 py-0.5 text-[9px]/3 leading-none font-bold text-primary-comfy-ink"
>
<span class="ppformula-text-center inline-block skew-x-12">{{
item.badge

View File

@@ -1,13 +1,21 @@
<script setup lang="ts">
import { cn } from '@comfyorg/tailwind-utils'
const { title, description, cta, href, bg } = defineProps<{
import type { CtaButton } from '../../scripts/posthog'
import { captureCtaClick } from '../../scripts/posthog'
const { title, description, cta, href, bg, ctaButton } = defineProps<{
title: string
description: string
cta: string
href: string
bg: string
ctaButton?: CtaButton
}>()
function onClick() {
if (ctaButton) captureCtaClick(ctaButton, 'products_section')
}
</script>
<template>
@@ -19,6 +27,7 @@ const { title, description, cta, href, bg } = defineProps<{
bg
)
"
@click="onClick"
>
<h3 class="text-3xl font-light whitespace-pre-line text-white lg:text-4xl">
{{ title }}

View File

@@ -5,11 +5,19 @@ import { cn } from '@comfyorg/tailwind-utils'
import { getRoutes } from '../../config/routes'
import { t } from '../../i18n/translations'
import type { CtaButton } from '../../scripts/posthog'
import ProductCard from './ProductCard.vue'
import SectionLabel from './SectionLabel.vue'
type Product = 'local' | 'cloud' | 'api' | 'enterprise'
const ctaButtonByProduct: Record<Product, CtaButton> = {
local: 'download_desktop',
cloud: 'comfy_cloud',
api: 'products',
enterprise: 'products'
}
const {
locale = 'en',
excludeProduct,
@@ -29,7 +37,8 @@ function cardDef(product: Product, href: string, bg: string) {
description: t(`products.${product}.description`, locale),
cta: t(`products.${product}.cta`, locale),
href,
bg
bg,
ctaButton: ctaButtonByProduct[product]
}
}

View File

@@ -11,6 +11,8 @@ import { nextTick, onMounted, ref } from 'vue'
import type { Locale } from '../../i18n/translations'
import { t } from '../../i18n/translations'
import { externalLinks, getRoutes } from '../../config/routes'
import type { CtaButton } from '../../scripts/posthog'
import { captureCtaClick } from '../../scripts/posthog'
import BrandButton from './BrandButton.vue'
import GitHubStarBadge from './GitHubStarBadge.vue'
import MobileMenu from './MobileMenu.vue'
@@ -27,8 +29,16 @@ const navLinks: NavLink[] = [
{
label: t('nav.products', locale),
items: [
{ label: t('nav.comfyLocal', locale), href: routes.download },
{ label: t('nav.comfyCloud', locale), href: routes.cloud },
{
label: t('nav.comfyLocal', locale),
href: routes.download,
ctaButton: 'comfy_desktop'
},
{
label: t('nav.comfyCloud', locale),
href: routes.cloud,
ctaButton: 'comfy_cloud'
},
{
label: t('nav.comfyApi', locale),
href: routes.api,
@@ -37,7 +47,11 @@ const navLinks: NavLink[] = [
{ label: t('nav.comfyEnterprise', locale), href: routes.cloudEnterprise }
]
},
{ label: t('nav.pricing', locale), href: routes.cloudPricing },
{
label: t('nav.pricing', locale),
href: routes.cloudPricing,
ctaButton: 'pricing'
},
{
label: t('nav.community', locale),
items: [
@@ -90,20 +104,29 @@ const navLinks: NavLink[] = [
}
]
const ctaButtons = [
const ctaButtons: {
label: string
prefix: string
core: string
href: string
primary: boolean
ctaButton: CtaButton
}[] = [
{
label: t('nav.downloadLocal', locale),
prefix: 'DOWNLOAD',
core: 'DESKTOP',
href: routes.download,
primary: false
primary: false,
ctaButton: 'download_desktop'
},
{
label: t('nav.launchCloud', locale),
prefix: 'LAUNCH',
core: 'CLOUD',
href: externalLinks.cloud,
primary: true
primary: true,
ctaButton: 'launch_cloud'
}
]
@@ -218,6 +241,7 @@ onMounted(() => {
:variant="cta.primary ? 'solid' : 'outline'"
size="nav"
:aria-label="cta.label"
@click="captureCtaClick(cta.ctaButton, 'nav')"
>
<span
class="inline-block max-w-0 overflow-hidden align-bottom transition-[max-width] duration-300 ease-in-out xl:max-w-28"

View File

@@ -5,6 +5,7 @@ import type { Locale } from '../../i18n/translations'
import { externalLinks } from '../../config/routes'
import { useHeroLogo } from '../../composables/useHeroLogo'
import { t } from '../../i18n/translations'
import { captureCtaClick } from '../../scripts/posthog'
import BrandButton from '../common/BrandButton.vue'
const { locale = 'en' } = defineProps<{ locale?: Locale }>()
@@ -47,6 +48,7 @@ const { loaded: logoLoaded } = useHeroLogo(logoContainer)
variant="outline"
size="lg"
class="mt-8 w-full p-4 uppercase lg:w-auto lg:min-w-60"
@click="captureCtaClick('run_first_workflow', 'hero')"
>
{{ t('hero.runFirstWorkflow', locale) }}
</BrandButton>

View File

@@ -78,3 +78,28 @@ describe('captureDownloadClick', () => {
expect(hoisted.mockCapture).not.toHaveBeenCalled()
})
})
describe('captureCtaClick', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.resetModules()
})
it('captures the CTA event with button and location', async () => {
const { initPostHog, captureCtaClick } = await import('./posthog')
initPostHog()
captureCtaClick('launch_cloud', 'nav')
expect(hoisted.mockCapture).toHaveBeenCalledWith('website:cta_clicked', {
button: 'launch_cloud',
location: 'nav'
})
})
it('does not capture before PostHog is initialized', async () => {
const { captureCtaClick } = await import('./posthog')
captureCtaClick('run_first_workflow', 'hero')
expect(hoisted.mockCapture).not.toHaveBeenCalled()
})
})

View File

@@ -49,3 +49,23 @@ export function captureDownloadClick(platform: Platform) {
console.error('PostHog download click capture failed', error)
}
}
export type CtaButton =
| 'launch_cloud'
| 'download_desktop'
| 'pricing'
| 'run_first_workflow'
| 'products'
| 'comfy_desktop'
| 'comfy_cloud'
export type CtaLocation = 'nav' | 'hero' | 'products_section'
export function captureCtaClick(button: CtaButton, location: CtaLocation) {
if (!initialized) return
try {
posthog.capture('website:cta_clicked', { button, location })
} catch (error) {
console.error('PostHog CTA click capture failed', error)
}
}