Skip to content

Commit 5d66a1d

Browse files
committed
PostHog and layout enhancement
1 parent a9e9731 commit 5d66a1d

6 files changed

Lines changed: 500 additions & 8 deletions

File tree

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ GOOGLE_AI_API_KEY=your-google-ai-api-key
7373
# Defaults to https://aichitect.dev in production.
7474
# NEXT_PUBLIC_SITE_URL=https://your-domain.com
7575

76+
# ------------------------------------------------------------
77+
# PostHog — product analytics
78+
# Find these in: PostHog → Project Settings → Project API Key
79+
# ------------------------------------------------------------
80+
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=your-posthog-project-token
81+
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
82+
7683
# MCP API key — gates the /api/mcp endpoint when set.
7784
# If unset, the endpoint is open (safe for local dev and existing MCP integrations).
7885
# Set to any random secret string to enforce auth on the public MCP surface.

app/layout.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Analytics } from "@vercel/analytics/next";
77
import { SpeedInsights } from "@vercel/speed-insights/next";
88
import { SuggestToolProvider } from "@/components/ui/SuggestToolContext";
99
import { WalkthroughProvider } from "@/components/ui/WalkthroughContext";
10+
import { PostHogProvider } from "@/components/PostHogProvider";
1011

1112
const inter = Inter({ subsets: ["latin"] });
1213

@@ -118,13 +119,15 @@ export default async function RootLayout({ children }: { children: React.ReactNo
118119
/>
119120
</head>
120121
<body className={inter.className}>
121-
<WalkthroughProvider>
122-
<SuggestToolProvider>
123-
{children}
124-
<Analytics />
125-
<SpeedInsights />
126-
</SuggestToolProvider>
127-
</WalkthroughProvider>
122+
<PostHogProvider>
123+
<WalkthroughProvider>
124+
<SuggestToolProvider>
125+
{children}
126+
<Analytics />
127+
<SpeedInsights />
128+
</SuggestToolProvider>
129+
</WalkthroughProvider>
130+
</PostHogProvider>
128131
</body>
129132
</html>
130133
);

components/PostHogProvider.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client";
2+
3+
import posthog from "posthog-js";
4+
import { PostHogProvider as PHProvider, usePostHog } from "posthog-js/react";
5+
import { usePathname, useSearchParams } from "next/navigation";
6+
import { Suspense, useEffect } from "react";
7+
8+
function PostHogPageView() {
9+
const pathname = usePathname();
10+
const searchParams = useSearchParams();
11+
const ph = usePostHog();
12+
13+
useEffect(() => {
14+
if (!ph) return;
15+
const qs = searchParams.toString();
16+
const url = window.origin + pathname + (qs ? `?${qs}` : "");
17+
ph.capture("$pageview", { $current_url: url });
18+
}, [pathname, searchParams, ph]);
19+
20+
return null;
21+
}
22+
23+
export function PostHogProvider({ children }: { children: React.ReactNode }) {
24+
useEffect(() => {
25+
const key = process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN;
26+
const host = process.env.NEXT_PUBLIC_POSTHOG_HOST;
27+
if (!key || !host) return;
28+
posthog.init(key, {
29+
api_host: host,
30+
person_profiles: "identified_only",
31+
capture_pageview: false,
32+
capture_pageleave: true,
33+
});
34+
}, []);
35+
36+
return (
37+
<PHProvider client={posthog}>
38+
<Suspense fallback={null}>
39+
<PostHogPageView />
40+
</Suspense>
41+
{children}
42+
</PHProvider>
43+
);
44+
}

components/ui/Navbar.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from "@/components/icons";
2323
import { useUser } from "@/hooks/useUser";
2424
import { createSupabaseBrowserClient } from "@/lib/db";
25+
import { usePostHog } from "posthog-js/react";
2526

2627
const VIEWS = [
2728
{ href: "/stacks", label: "Stacks", Icon: IconLayers },
@@ -84,9 +85,23 @@ export default function Navbar({ counts }: { counts?: Counts }) {
8485
const { openSuggest } = useSuggestTool();
8586
const { openWalkthrough } = useWalkthrough();
8687
const { user, loading: userLoading, signIn, signOut } = useUser();
88+
const ph = usePostHog();
8789

8890
const username = user?.user_metadata?.user_name as string | undefined;
8991

92+
useEffect(() => {
93+
if (!ph) return;
94+
if (user) {
95+
ph.identify(user.id, {
96+
github_username: user.user_metadata?.user_name as string | undefined,
97+
avatar_url: user.user_metadata?.avatar_url as string | undefined,
98+
email: user.email,
99+
});
100+
} else {
101+
ph.reset();
102+
}
103+
}, [user, ph]);
104+
90105
useEffect(() => {
91106
if (!user?.id) return;
92107
const supabase = createSupabaseBrowserClient();

0 commit comments

Comments
 (0)