Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions apps/website/src/components/common/NavDesktopLink.vue
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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"
Expand All @@ -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'
)
"
>
Expand All @@ -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>
Expand All @@ -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
Expand Down
11 changes: 10 additions & 1 deletion apps/website/src/components/common/ProductCard.vue
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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 }}
Expand Down
11 changes: 10 additions & 1 deletion apps/website/src/components/common/ProductCardsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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]
}
}

Expand Down
36 changes: 30 additions & 6 deletions apps/website/src/components/common/SiteNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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,
Expand All @@ -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: [
Expand Down Expand Up @@ -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'
}
]

Expand Down Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions apps/website/src/components/home/HeroSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>()
Expand Down Expand Up @@ -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>
Expand Down
25 changes: 25 additions & 0 deletions apps/website/src/scripts/posthog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
20 changes: 20 additions & 0 deletions apps/website/src/scripts/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading