Skip to content

Commit 3ee08e2

Browse files
committed
posthog migration
1 parent 94e2cbc commit 3ee08e2

File tree

172 files changed

+1041
-3088
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+1041
-3088
lines changed

apps/dashboard/.env.example

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ NEXT_PUBLIC_DASHBOARD_UPLOAD_SERVER="https://storage.thirdweb-dev.com"
3535
# - not required to build (unless testing contract search)
3636
NEXT_PUBLIC_TYPESENSE_CONTRACT_API_KEY=
3737

38-
# posthog API key
39-
# - not required for prod/staging
40-
NEXT_PUBLIC_POSTHOG_API_KEY="ignored"
41-
4238
# Stripe Customer portal
4339
NEXT_PUBLIC_STRIPE_KEY=
4440

@@ -108,4 +104,8 @@ STRIPE_SECRET_KEY=""
108104

109105
# required for server wallet management
110106
NEXT_PUBLIC_THIRDWEB_VAULT_URL=""
111-
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
107+
NEXT_PUBLIC_ENGINE_CLOUD_URL=""
108+
109+
# posthog setup
110+
NEXT_PUBLIC_POSTHOG_KEY=""
111+
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com

apps/dashboard/.eslintrc.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ module.exports = {
110110
message:
111111
'This is likely a mistake. If you really want to import this - postfix the imported name with Icon. Example - "LinkIcon"',
112112
},
113+
{
114+
name: "posthog-js",
115+
message:
116+
'Import "posthog-js" directly only within the analytics helpers ("src/@/analytics/*"). Use the exported helpers from "@/analytics/track" elsewhere.',
117+
},
113118
],
114119
},
115120
],
@@ -139,6 +144,13 @@ module.exports = {
139144
"no-restricted-imports": ["off"],
140145
},
141146
},
147+
// allow direct PostHog imports inside analytics helpers
148+
{
149+
files: "src/@/analytics/**/*",
150+
rules: {
151+
"no-restricted-imports": ["off"],
152+
},
153+
},
142154
// enable rule specifically for TypeScript files
143155
{
144156
files: ["*.ts", "*.tsx"],
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import posthog from "posthog-js";
2+
3+
const NEXT_PUBLIC_POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY;
4+
5+
if (NEXT_PUBLIC_POSTHOG_KEY) {
6+
posthog.init(NEXT_PUBLIC_POSTHOG_KEY, {
7+
api_host: "/_ph",
8+
ui_host: "https://us.posthog.com",
9+
capture_pageview: "history_change",
10+
capture_pageleave: "if_capture_pageview",
11+
// disable exception capture (for now)
12+
capture_exceptions: false,
13+
// specifically disable autocapture (does not affect pageview capture)
14+
autocapture: false,
15+
debug: process.env.NODE_ENV === "development",
16+
});
17+
}

apps/dashboard/knip.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@thirdweb-dev/service-utils",
1414
"@thirdweb-dev/vault-sdk",
1515
"@types/color",
16-
"fast-xml-parser"
16+
"fast-xml-parser",
17+
"posthog-node"
1718
]
1819
}

apps/dashboard/next.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ const baseNextConfig: NextConfig = {
146146
},
147147
async rewrites() {
148148
return [
149+
{
150+
source: "/_ph/static/:path*",
151+
destination: "https://us-assets.i.posthog.com/static/:path*",
152+
},
153+
{
154+
source: "/_ph/:path*",
155+
destination: "https://us.i.posthog.com/:path*",
156+
},
157+
{
158+
source: "/_ph/decide",
159+
destination: "https://us.i.posthog.com/decide",
160+
},
149161
{
150162
source: "/thirdweb.eth",
151163
destination: "/deployer.thirdweb.eth",
@@ -173,6 +185,8 @@ const baseNextConfig: NextConfig = {
173185
]),
174186
];
175187
},
188+
// This is required to support PostHog trailing slash API requests
189+
skipTrailingSlashRedirect: true,
176190
images: {
177191
dangerouslyAllowSVG: true,
178192
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",

apps/dashboard/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"date-fns": "4.1.0",
6464
"fast-xml-parser": "^5.2.5",
6565
"fetch-event-stream": "0.1.5",
66-
"flat": "^6.0.1",
6766
"framer-motion": "12.17.0",
6867
"fuse.js": "7.1.0",
6968
"input-otp": "^1.4.1",
@@ -78,7 +77,8 @@
7877
"p-limit": "^6.2.0",
7978
"papaparse": "^5.5.3",
8079
"pluralize": "^8.0.0",
81-
"posthog-js": "1.67.1",
80+
"posthog-js": "1.252.0",
81+
"posthog-node": "5.1.0",
8282
"prettier": "3.5.3",
8383
"qrcode": "^1.5.3",
8484
"react": "19.1.0",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import posthog from "posthog-js";
4+
import { useEffect } from "react";
5+
import type { Account } from "../../@3rdweb-sdk/react/hooks/useApi";
6+
7+
const warnedMessages = new Set<string>();
8+
function warnOnce(message: string) {
9+
if (warnedMessages.has(message)) {
10+
return;
11+
}
12+
warnedMessages.add(message);
13+
console.warn(message);
14+
}
15+
16+
export function AccountIdentifier(props: {
17+
account: Pick<Account, "id" | "email">;
18+
}) {
19+
// eslint-disable-next-line no-restricted-syntax
20+
useEffect(() => {
21+
if (!posthog.__loaded) {
22+
warnOnce(
23+
"[DASHBOARD_ANALYTICS] is not initialized, cannot identify user",
24+
);
25+
return;
26+
}
27+
posthog.identify(props.account.id, {
28+
...(props.account.email ? { email: props.account.email } : {}),
29+
});
30+
}, [props.account.id, props.account.email]);
31+
return null;
32+
}
33+
34+
export function TeamIdentifier(props: {
35+
teamId: string;
36+
}) {
37+
// eslint-disable-next-line no-restricted-syntax
38+
useEffect(() => {
39+
if (!posthog.__loaded) {
40+
warnOnce(
41+
"[DASHBOARD_ANALYTICS] is not initialized, cannot identify team",
42+
);
43+
return;
44+
}
45+
posthog.group("team", props.teamId);
46+
}, [props.teamId]);
47+
return null;
48+
}
49+
50+
export function reset() {
51+
if (!posthog.__loaded) {
52+
warnOnce("[DASHBOARD_ANALYTICS] is not initialized, cannot reset");
53+
return;
54+
}
55+
posthog.reset();
56+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Central entry point for analytics tracking helpers.
3+
*
4+
* This file re-exports individual event *categories* located under
5+
* `./track/*`. Each category file in turn exposes small helper functions that
6+
* call PostHog with type-safe payloads.
7+
*
8+
* Example – reporting a contract deployment:
9+
* ```ts
10+
* import { reportContractDeployed } from "@/analytics/track";
11+
*
12+
* reportContractDeployed({
13+
* address: "0x…",
14+
* chainId: 1,
15+
* });
16+
* ```
17+
*
18+
* Adding a new event *category*:
19+
* 1. Create a new file under `./track/<category>.ts`.
20+
* 2. Inside that file, follow the pattern shown in `contract.ts` to define
21+
* Zod schemas + `reportWhatever` helpers.
22+
* 3. Add a star-export below so the helpers are surfaced at the package root:
23+
* ```ts
24+
* export * from "./track/<category>";
25+
* ```
26+
* 4. Consumers can now `import { reportMyEvent } from "@/analytics/track";`.
27+
*/
28+
29+
export * from "./track/contract";
30+
export * from "./track/token";
31+
export * from "./track/nft";
32+
export * from "./track/onboarding";
33+
export * from "./track/marketplace";
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Analytics Tracking Helpers
2+
3+
This folder contains **type-safe wrappers** around our analytics provider (PostHog).
4+
Instead of calling `posthog.capture` directly, feature code should import the
5+
pre-defined helpers from here. This guarantees that:
6+
7+
1. Event names are consistent.
8+
2. Payloads adhere to a strict schema (validated at runtime via Zod and typed at
9+
compile time).
10+
11+
---
12+
13+
## Quick start
14+
15+
```ts
16+
import { reportContractDeployed } from "@/analytics/track";
17+
18+
// Contract deployment example
19+
reportContractDeployed({
20+
address: "0x…",
21+
chainId: 1,
22+
});
23+
```
24+
25+
> **Note** Ensure that PostHog is initialised *before* you emit events. Our
26+
> bootstrapping code does this automatically during app start-up.
27+
28+
---
29+
30+
## Project structure
31+
32+
```
33+
track/ # ← you are here
34+
├─ __internal.ts # low-level wrapper around posthog.capture (do NOT use)
35+
├─ contract.ts # "contract" event category helpers
36+
├─ README.md # this file
37+
└─ … # future categories live here
38+
```
39+
40+
* `__internal.ts` exposes `__internal__reportEvent` which performs the actual
41+
PostHog call and safeguards against the SDK not being ready.
42+
* Every **category file** (such as `contract.ts`) groups together related events.
43+
Each event is represented by a `report<Something>` function.
44+
* `track.ts` sits one directory up and **re-exports all helper functions** so
45+
consumers can import the ones they need directly:
46+
47+
```ts
48+
import { reportContractDeployed /*, reportOtherEvent */ } from "@/analytics/track";
49+
```
50+
51+
---
52+
53+
## Adding a **new event** to an existing category
54+
55+
1. Open the relevant category file (e.g. `contract.ts`).
56+
2. Define a new Zod schema describing the payload:
57+
58+
```ts
59+
const ContractUpgradeSchema = z.object({
60+
address: z.string(),
61+
oldVersion: z.string(),
62+
newVersion: z.string(),
63+
});
64+
```
65+
66+
3. Export a reporting helper that forwards the validated payload:
67+
68+
```ts
69+
export function reportContractUpgraded(
70+
payload: z.infer<typeof ContractUpgradeSchema>,
71+
) {
72+
__internal__reportEvent("contract upgraded", payload);
73+
}
74+
```
75+
76+
That's it – consumers can now call `track.contract.reportContractUpgraded(...)`.
77+
78+
---
79+
80+
## Adding a **new category**
81+
82+
1. Create a new file `track/<category>.ts`.
83+
2. Follow the same pattern as in `contract.ts` (define schemas + export helper
84+
functions).
85+
3. Open `track.ts` (one directory up) and add a star-export so the helpers are
86+
surfaced at the package root:
87+
88+
```ts
89+
export * from "./track/<category>";
90+
```
91+
92+
Your new helpers can now be imported directly:
93+
94+
```ts
95+
import { reportMyNewEvent } from "@/analytics/track";
96+
```
97+
98+
---
99+
100+
## Conventions & best practices
101+
102+
* **Lowercase, space-separated event names** – keep them human-readable.
103+
* **Small, focused payloads** – only include properties that are useful for
104+
analytics.
105+
* **Avoid calling PostHog directly** – always go through `__internal__reportEvent`
106+
so we keep a single choke-point.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import posthog, { type Properties } from "posthog-js";
2+
3+
/**
4+
* Low-level wrapper around `posthog.capture` with a safety-check that waits
5+
* until PostHog has been initialised.
6+
*
7+
* **⚠️ INTERNAL USE ONLY** – Do not call this directly from feature code.
8+
* Instead, create a domain-specific helper (see `contract.ts`) so we keep a
9+
* single source of truth for event names & payload contracts.
10+
*/
11+
12+
export function __internal__reportEvent(
13+
eventName: string,
14+
properties?: Properties | null,
15+
) {
16+
if (!posthog.__loaded) {
17+
console.warn(
18+
"[DASHBOARD_ANALYTICS] is not initialized, cannot track event",
19+
);
20+
return;
21+
}
22+
posthog.capture(eventName, properties);
23+
}

0 commit comments

Comments
 (0)