Skip to content

Commit d0411e4

Browse files
committed
freebuff web: Add posthog analytics
1 parent 12e8691 commit d0411e4

File tree

9 files changed

+238
-24
lines changed

9 files changed

+238
-24
lines changed

bun.lock

Lines changed: 103 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/src/constants/analytics-events.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ export enum AnalyticsEvent {
147147
CHATGPT_OAUTH_RATE_LIMITED = 'sdk.chatgpt_oauth_rate_limited',
148148
CHATGPT_OAUTH_AUTH_ERROR = 'sdk.chatgpt_oauth_auth_error',
149149

150+
// Freebuff - Get Started Page
151+
FREEBUFF_GET_STARTED_VIEWED = 'freebuff.get_started_viewed',
152+
FREEBUFF_GET_STARTED_HELP_EXPANDED = 'freebuff.get_started_help_expanded',
153+
FREEBUFF_GET_STARTED_EDITOR_CLICKED = 'freebuff.get_started_editor_clicked',
154+
155+
// Freebuff - Home Page
156+
FREEBUFF_HOME_INSTALL_COMMAND_COPIED = 'freebuff.home_install_command_copied',
157+
FREEBUFF_HOME_GITHUB_CLICKED = 'freebuff.home_github_clicked',
158+
FREEBUFF_HOME_INSTALL_GUIDE_EXPANDED = 'freebuff.home_install_guide_expanded',
159+
FREEBUFF_HOME_FAQ_OPENED = 'freebuff.home_faq_opened',
160+
150161
// Common
151162
FLUSH_FAILED = 'common.flush_failed',
152163

freebuff/web/next.config.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ const nextConfig = {
6969
]
7070
},
7171
reactStrictMode: false,
72+
async rewrites() {
73+
return [
74+
{
75+
source: '/ingest/static/:path*',
76+
destination: 'https://us-assets.i.posthog.com/static/:path*',
77+
},
78+
{
79+
source: '/ingest/:path*',
80+
destination: 'https://us.i.posthog.com/:path*',
81+
},
82+
]
83+
},
7284
}
7385

7486
export default nextConfig

freebuff/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"next-auth": "^4.24.11",
2626
"next-themes": "^0.4.6",
2727
"pino": "^9.6.0",
28+
"posthog-js": "^1.363.3",
2829
"react": "^19.0.0",
2930
"react-dom": "^19.0.0",
3031
"tailwind-merge": "^2.5.2",

freebuff/web/src/app/get-started/get-started-client.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
34
import { AnimatePresence, motion } from 'framer-motion'
45
import {
56
ChevronDown,
@@ -9,6 +10,7 @@ import {
910
} from 'lucide-react'
1011
import Image from 'next/image'
1112
import Link from 'next/link'
13+
import posthog from 'posthog-js'
1214
import { useEffect, useState } from 'react'
1315

1416
import { BackgroundBeams } from '@/components/background-beams'
@@ -98,7 +100,10 @@ export default function GetStartedClient({
98100

99101
useEffect(() => {
100102
setOs(detectOS())
101-
}, [])
103+
posthog.capture(AnalyticsEvent.FREEBUFF_GET_STARTED_VIEWED, {
104+
referrer: referrerName,
105+
})
106+
}, [referrerName])
102107

103108
return (
104109
<div className="relative min-h-screen">
@@ -145,7 +150,7 @@ export default function GetStartedClient({
145150
</motion.div>
146151

147152
{/* Main content */}
148-
<div className="relative z-10 container mx-auto px-4 pt-28 pb-16 md:pt-36 md:pb-24 flex flex-col items-center">
153+
<div className="relative z-10 container mx-auto px-4 pt-16 pb-16 md:pt-36 md:pb-24 flex flex-col items-center">
149154
<div className="w-full max-w-2xl">
150155
<div className="bg-background/80 backdrop-blur-sm border border-zinc-800 rounded-xl overflow-hidden">
151156
{/* Header */}
@@ -180,7 +185,14 @@ export default function GetStartedClient({
180185
{/* Collapsible help */}
181186
<div className="rounded-lg overflow-hidden">
182187
<button
183-
onClick={() => setHelpExpanded(!helpExpanded)}
188+
onClick={() => {
189+
if (!helpExpanded) {
190+
posthog.capture(
191+
AnalyticsEvent.FREEBUFF_GET_STARTED_HELP_EXPANDED,
192+
)
193+
}
194+
setHelpExpanded(!helpExpanded)
195+
}}
184196
className="w-full flex items-center justify-between px-4 py-3 text-sm text-muted-foreground hover:text-foreground hover:bg-zinc-800/50 transition-colors cursor-pointer"
185197
>
186198
<span>Need help setting up?</span>
@@ -210,9 +222,16 @@ export default function GetStartedClient({
210222
</p>
211223
<div className="grid grid-cols-2 gap-2">
212224
{editors.map((editor) => (
213-
<div
225+
<button
214226
key={editor.name}
215-
className="flex items-center gap-2 px-3 py-2 bg-zinc-800/60 border border-zinc-700/40 rounded-lg hover:border-zinc-600 transition-colors duration-200 cursor-default"
227+
type="button"
228+
className="flex items-center gap-2 px-3 py-2 bg-zinc-800/60 border border-zinc-700/40 rounded-lg hover:border-zinc-600 transition-colors duration-200 cursor-pointer"
229+
onClick={() =>
230+
posthog.capture(
231+
AnalyticsEvent.FREEBUFF_GET_STARTED_EDITOR_CLICKED,
232+
{ editor: editor.name },
233+
)
234+
}
216235
>
217236
<div
218237
className={cn(
@@ -231,7 +250,7 @@ export default function GetStartedClient({
231250
<span className="text-sm font-medium text-zinc-200">
232251
{editor.name}
233252
</span>
234-
</div>
253+
</button>
235254
))}
236255
</div>
237256
</div>

freebuff/web/src/app/get-started/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@ import type { Metadata } from 'next'
44

55
import { siteConfig } from '@/lib/constant'
66

7+
function normalizeReferrer(raw: string | undefined): string | null {
8+
if (!raw) return null
9+
const trimmed = raw.trim().slice(0, 50)
10+
return trimmed || null
11+
}
12+
713
export async function generateMetadata({
814
searchParams,
915
}: {
1016
searchParams: Promise<{ referrer?: string }>
1117
}): Promise<Metadata> {
1218
const resolvedSearchParams = await searchParams
13-
const referrerName = resolvedSearchParams.referrer
19+
const referrerName = normalizeReferrer(resolvedSearchParams.referrer)
1420
const title = referrerName
1521
? `${referrerName} invited you to try Freebuff!`
1622
: 'Get Started with Freebuff'
@@ -27,7 +33,7 @@ export default async function GetStartedPage({
2733
searchParams: Promise<{ referrer?: string }>
2834
}) {
2935
const resolvedSearchParams = await searchParams
30-
const referrerName = resolvedSearchParams.referrer?.slice(0, 50) ?? null
36+
const referrerName = normalizeReferrer(resolvedSearchParams.referrer)
3137

3238
return <GetStartedClient referrerName={referrerName} />
3339
}

freebuff/web/src/app/home-client.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client'
22

3+
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
34
import { AnimatePresence, motion } from 'framer-motion'
45
import {
56
Check,
@@ -8,6 +9,7 @@ import {
89
} from 'lucide-react'
910
import Image from 'next/image'
1011
import Link from 'next/link'
12+
import posthog from 'posthog-js'
1113
import { useMemo, useState } from 'react'
1214

1315
import { BackgroundBeams } from '@/components/background-beams'
@@ -78,7 +80,14 @@ function SetupGuide() {
7880
return (
7981
<div className="max-w-md mx-auto">
8082
<button
81-
onClick={() => setIsOpen(!isOpen)}
83+
onClick={() => {
84+
if (!isOpen) {
85+
posthog.capture(
86+
AnalyticsEvent.FREEBUFF_HOME_INSTALL_GUIDE_EXPANDED,
87+
)
88+
}
89+
setIsOpen(!isOpen)
90+
}}
8291
aria-expanded={isOpen}
8392
className="flex items-center gap-2 mx-auto text-sm text-zinc-400 hover:text-acid-matrix transition-colors duration-200 cursor-pointer group"
8493
>
@@ -152,6 +161,7 @@ function InstallCommand({ className }: { className?: string }) {
152161
navigator.clipboard.writeText(INSTALL_COMMAND)
153162
setCopied(true)
154163
setCopyCount(c => c + 1)
164+
posthog.capture(AnalyticsEvent.FREEBUFF_HOME_INSTALL_COMMAND_COPIED)
155165
setTimeout(() => setCopied(false), 1800)
156166
}
157167

@@ -257,7 +267,15 @@ function FAQList() {
257267
)}
258268
>
259269
<button
260-
onClick={() => setOpenIndex(isOpen ? null : i)}
270+
onClick={() => {
271+
if (!isOpen) {
272+
posthog.capture(
273+
AnalyticsEvent.FREEBUFF_HOME_FAQ_OPENED,
274+
{ question: faq.question },
275+
)
276+
}
277+
setOpenIndex(isOpen ? null : i)
278+
}}
261279
className="w-full flex items-center gap-4 px-4 py-5 text-left transition-all duration-300 cursor-pointer group"
262280
>
263281
<span
@@ -425,6 +443,9 @@ export default function HomeClient() {
425443
target="_blank"
426444
rel="noopener noreferrer"
427445
className="relative font-medium px-3 py-2 rounded-md transition-all duration-200 text-zinc-400 hover:text-white flex items-center gap-2 text-sm"
446+
onClick={() =>
447+
posthog.capture(AnalyticsEvent.FREEBUFF_HOME_GITHUB_CLICKED)
448+
}
428449
>
429450
<Icons.github className="h-4 w-4" />
430451
<span className="hidden sm:inline">GitHub</span>

freebuff/web/src/app/layout.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Footer } from '@/components/footer'
66
import { ThemeProvider } from '@/components/theme-provider'
77
import { siteConfig } from '@/lib/constant'
88
import { fonts } from '@/lib/fonts'
9+
import { PostHogProvider } from '@/lib/PostHogProvider'
910
import SessionProvider from '@/lib/SessionProvider'
1011
import { cn } from '@/lib/utils'
1112

@@ -53,8 +54,10 @@ export default function RootLayout({
5354
>
5455
<ThemeProvider attribute="class">
5556
<SessionProvider>
56-
<div className="flex-grow">{children}</div>
57-
<Footer />
57+
<PostHogProvider>
58+
<div className="flex-grow">{children}</div>
59+
<Footer />
60+
</PostHogProvider>
5861
</SessionProvider>
5962
</ThemeProvider>
6063
</body>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use client'
2+
3+
import { env } from '@codebuff/common/env'
4+
import { useSession } from 'next-auth/react'
5+
import posthog from 'posthog-js'
6+
import { PostHogProvider as PostHogProviderWrapper } from 'posthog-js/react'
7+
import { useEffect, useRef, type ReactNode } from 'react'
8+
9+
export function PostHogProvider({ children }: { children: ReactNode }) {
10+
const { data: session } = useSession()
11+
const prevSessionRef = useRef(session)
12+
13+
useEffect(() => {
14+
if (!env.NEXT_PUBLIC_POSTHOG_API_KEY || typeof window === 'undefined') {
15+
return
16+
}
17+
18+
posthog.init(env.NEXT_PUBLIC_POSTHOG_API_KEY, {
19+
api_host: '/ingest',
20+
ui_host: env.NEXT_PUBLIC_POSTHOG_HOST_URL,
21+
person_profiles: 'always',
22+
})
23+
}, [])
24+
25+
useEffect(() => {
26+
if (!env.NEXT_PUBLIC_POSTHOG_API_KEY) {
27+
return
28+
}
29+
30+
const hadSession = !!prevSessionRef.current?.user?.email
31+
const hasSession = !!session?.user?.email
32+
prevSessionRef.current = session
33+
34+
if (hasSession && session.user) {
35+
posthog.identify(session.user.email!, {
36+
email: session.user.email,
37+
user_id: session.user.id,
38+
name: session.user.name,
39+
})
40+
} else if (hadSession && !hasSession) {
41+
posthog.reset()
42+
}
43+
}, [session])
44+
45+
return (
46+
<PostHogProviderWrapper client={posthog}>
47+
{children}
48+
</PostHogProviderWrapper>
49+
)
50+
}

0 commit comments

Comments
 (0)