Skip to content

Commit 57a8fec

Browse files
committed
posthog migration: part 4
1 parent a1aec23 commit 57a8fec

File tree

11 files changed

+320
-156
lines changed

11 files changed

+320
-156
lines changed

.cursor/rules/dashboard.mdc

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ description: Rules for writing features in apps/dashboard
33
globs: dashboard
44
alwaysApply: false
55
---
6-
76
# Reusable Core UI Components
87

98
- Always import from the central UI library under `@/components/ui/*` – e.g. `import { Button } from "@/components/ui/button"`.
@@ -101,3 +100,29 @@ Guidelines:
101100
- Keep `queryKey` stable and descriptive for cache hits.
102101
- Prefer API routes or server actions to keep tokens secret; the browser only sees relative paths.
103102
- Configure `staleTime` / `cacheTime` according to freshness requirements.
103+
104+
# Analytics Event Reporting
105+
106+
- **Add events intentionally** – only when they answer a concrete product/business question.
107+
- **Event name**: human-readable `<subject> <verb>` phrase (e.g. `"contract deployed"`).
108+
- **Reporting helper**: `report<Subject><Verb>` (PascalCase); all live in `src/@/analytics/report.ts`.
109+
- **Mandatory JSDoc**: explain *Why* the event exists and *Who* owns it (`@username`).
110+
- **Typed properties**: accept a single `properties` object and pass it unchanged to `posthog.capture`.
111+
- **Client-side only**: never import `posthog-js` in server components.
112+
- **Housekeeping**: ping **#core-services** before renaming or removing an event.
113+
114+
```ts
115+
/**
116+
* ### Why do we need to report this event?
117+
* - Tracks number of contracts deployed
118+
*
119+
* ### Who is responsible for this event?
120+
* @jnsdls
121+
*/
122+
export function reportContractDeployed(properties: {
123+
address: string;
124+
chainId: number;
125+
}) {
126+
posthog.capture("contract deployed", properties);
127+
}
128+
```

AGENTS.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,41 @@ Welcome, AI copilots! This guide captures the coding standards, architectural de
104104
-- Configure staleTime / cacheTime based on freshness requirements (default ≥ 60 s).
105105
-- Keep tokens secret by calling internal API routes or server actions.
106106

107+
6.5 Analytics Event Reporting
108+
109+
- **When to create a new event**
110+
-- Only add events that answer a clear product or business question.
111+
-- Check `src/@/analytics/report.ts` first; avoid duplicates.
112+
113+
- **Naming conventions**
114+
-- **Event name**: human-readable phrase in the form `<subject> <verb>` (e.g. "contract deployed").
115+
-- **Reporting function**: `report<Subject><Verb>` (PascalCase).
116+
-- All reporting helpers currently live in the shared `report.ts` file.
117+
118+
- **Boilerplate template**
119+
-- Add a JSDoc header explaining **Why** the event exists and **Who** owns it (`@username`).
120+
-- Accept a single typed `properties` object and forward it unchanged to `posthog.capture`.
121+
-- Example:
122+
123+
```ts
124+
/**
125+
* ### Why do we need to report this event?
126+
* - Tracks number of contracts deployed
127+
*
128+
* ### Who is responsible for this event?
129+
* @jnsdls
130+
*/
131+
export function reportContractDeployed(properties: {
132+
address: string;
133+
chainId: number;
134+
}) {
135+
posthog.capture("contract deployed", properties);
136+
}
137+
```
138+
139+
- **Client-side only**: never import `posthog-js` in server components.
140+
- **Housekeeping**: Inform **#core-services** before renaming or removing an existing event.
141+
107142
108143

109144
7. Performance & Bundle Size
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Analytics Guidelines
2+
3+
This folder centralises the **PostHog** tracking logic for the dashboard app.
4+
Most developers will only need to add or extend _event-reporting_ functions in `report.ts`.
5+
6+
---
7+
8+
## 1. When to add an event
9+
1. Ask yourself if the data will be **actionable**. Every event should have a clear product or business question it helps answer.
10+
2. Check if a similar event already exists in `report.ts`. Avoid duplicates.
11+
12+
---
13+
14+
## 2. Naming conventions
15+
| Concept | Convention | Example |
16+
|---------|------------|---------|
17+
| **Event name** (string sent to PostHog) | Human-readable phrase formatted as `<subject> <verb>` | `"contract deployed"` |
18+
| **Reporting function** | `report<Subject><Verb>` (PascalCase) | `reportContractDeployed` |
19+
| **File** | All event functions live in the shared `report.ts` file (for now) ||
20+
21+
> Keeping names predictable makes it easy to search both code and analytics.
22+
23+
---
24+
25+
## 3. Boilerplate / template
26+
Add a new function to `report.ts` following this pattern:
27+
28+
```ts
29+
/**
30+
* ### Why do we need to report this event?
31+
* - _Add bullet points explaining the product metrics/questions this event answers._
32+
*
33+
* ### Who is responsible for this event?
34+
* @your-github-handle
35+
*/
36+
export function reportExampleEvent(properties: {
37+
/* Add typed properties here */
38+
}) {
39+
posthog.capture("example event", {
40+
/* Pass the same properties here */
41+
});
42+
}
43+
```
44+
45+
Guidelines:
46+
1. **Explain the "why".** The JSDoc block is mandatory so future contributors know the purpose.
47+
2. **Type everything.** The `properties` object should be fully typed—this doubles as documentation.
48+
3. **Client-side only.** `posthog-js` must never run on the server. Call these reporting helpers from client components, event handlers, etc.
49+
50+
---
51+
52+
## 4. Editing or removing events
53+
1. Update both the function and the PostHog event definition (if required).
54+
2. Inform the core services team before removing or renaming an event.
55+
56+
---
57+
58+
## 5. Identification & housekeeping (FYI)
59+
Most devs can ignore this section, but for completeness:
60+
61+
- `hooks/identify-account.ts` and `hooks/identify-team.ts` wrap `posthog.identify`/`group` calls.
62+
- `resetAnalytics` clears identity state (used on logout).
63+
64+
---
65+
66+
## 6. Need help?
67+
Ping #core-services in slack.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import posthog from "posthog-js";
2+
3+
import type { Team } from "../api/team";
4+
5+
// ----------------------------
6+
// CONTRACTS
7+
// ----------------------------
8+
9+
/**
10+
* ### Why do we need to report this event?
11+
* - To track the number of contracts deployed
12+
* - To track the number of contracts deployed on each chain
13+
*
14+
* ### Who is responsible for this event?
15+
* @jnsdls
16+
*
17+
*/
18+
export function reportContractDeployed(properties: {
19+
address: string;
20+
chainId: number;
21+
}) {
22+
posthog.capture("contract deployed", {
23+
address: properties.address,
24+
chainId: properties.chainId,
25+
});
26+
}
27+
28+
// ----------------------------
29+
// ONBOARDING (TEAM)
30+
// ----------------------------
31+
32+
/**
33+
* ### Why do we need to report this event?
34+
* - To track the number of teams that enter the onboarding flow
35+
*
36+
* ### Who is responsible for this event?
37+
* @jnsdls
38+
*
39+
*/
40+
export function reportOnboardingStarted() {
41+
posthog.capture("onboarding started");
42+
}
43+
44+
/**
45+
* ### Why do we need to report this event?
46+
* - To track the number of teams that select a paid plan during onboarding
47+
* - To know **which** plan was selected
48+
*
49+
* ### Who is responsible for this event?
50+
* @jnsdls
51+
*
52+
*/
53+
export function reportOnboardingPlanSelected(properties: {
54+
plan: Team["billingPlan"];
55+
}) {
56+
posthog.capture("onboarding plan selected", {
57+
plan: properties.plan,
58+
});
59+
}
60+
61+
/**
62+
* ### Why do we need to report this event?
63+
* - To track the number of teams that skip the plan-selection step during onboarding
64+
*
65+
* ### Who is responsible for this event?
66+
* @jnsdls
67+
*
68+
*/
69+
export function reportOnboardingPlanSelectionSkipped() {
70+
posthog.capture("onboarding plan selection skipped");
71+
}
72+
73+
/**
74+
* ### Why do we need to report this event?
75+
* - To track the number of teams that invite members during onboarding
76+
* - To track **how many** members were invited
77+
*
78+
* ### Who is responsible for this event?
79+
* @jnsdls
80+
*
81+
*/
82+
export function reportOnboardingMembersInvited(properties: {
83+
count: number;
84+
}) {
85+
posthog.capture("onboarding members invited", {
86+
count: properties.count,
87+
});
88+
}
89+
90+
/**
91+
* ### Why do we need to report this event?
92+
* - To track the number of teams that skip inviting members during onboarding
93+
*
94+
* ### Who is responsible for this event?
95+
* @jnsdls
96+
*
97+
*/
98+
export function reportOnboardingMembersSkipped() {
99+
posthog.capture("onboarding members skipped");
100+
}
101+
102+
/**
103+
* ### Why do we need to report this event?
104+
* - To track how many teams click the upsell (upgrade) button on the member-invite step during onboarding
105+
*
106+
* ### Who is responsible for this event?
107+
* @jnsdls
108+
*
109+
*/
110+
export function reportOnboardingMembersUpsellButtonClicked() {
111+
posthog.capture("onboarding members upsell clicked");
112+
}
113+
114+
/**
115+
* ### Why do we need to report this event?
116+
* - To track which plan is selected from the members-step upsell during onboarding
117+
*
118+
* ### Who is responsible for this event?
119+
* @jnsdls
120+
*
121+
*/
122+
export function reportOnboardingMembersUpsellPlanSelected(properties: {
123+
plan: Team["billingPlan"];
124+
}) {
125+
posthog.capture("onboarding members upsell plan selected", {
126+
plan: properties.plan,
127+
});
128+
}
129+
130+
/**
131+
* ### Why do we need to report this event?
132+
* - To track the number of teams that completed onboarding
133+
*
134+
* ### Who is responsible for this event?
135+
* @jnsdls
136+
*
137+
*/
138+
export function reportOnboardingCompleted() {
139+
posthog.capture("onboarding completed");
140+
}

apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/_components/plan-selector.tsx

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"use client";
22

3+
import {
4+
reportOnboardingPlanSelected,
5+
reportOnboardingPlanSelectionSkipped,
6+
} from "@/analytics/report";
37
import type { Team } from "@/api/team";
48
import { PricingCard } from "@/components/blocks/pricing-card";
59
import { Button } from "@/components/ui/button";
610
import { Separator } from "@/components/ui/separator";
711
import { useDashboardRouter } from "@/lib/DashboardRouter";
8-
import { useTrack } from "hooks/analytics/useTrack";
912
import Link from "next/link";
1013
import { pollWithTimeout } from "utils/pollWithTimeout";
1114
import { useStripeRedirectEvent } from "../../../../../(stripe)/stripe-redirect/stripeRedirectChannel";
@@ -14,7 +17,6 @@ export function PlanSelector(props: {
1417
team: Team;
1518
getTeam: () => Promise<Team>;
1619
}) {
17-
const trackEvent = useTrack();
1820
const router = useDashboardRouter();
1921

2022
useStripeRedirectEvent(async () => {
@@ -25,12 +27,6 @@ export function PlanSelector(props: {
2527
const isNonFreePlan = team.billingPlan !== "free";
2628

2729
if (isNonFreePlan) {
28-
trackEvent({
29-
category: "teamOnboarding",
30-
action: "upgradePlan",
31-
label: "success",
32-
plan: team.billingPlan,
33-
});
3430
router.replace(`/get-started/team/${props.team.slug}/add-members`);
3531
}
3632

@@ -49,10 +45,7 @@ export function PlanSelector(props: {
4945
label: "Get Started",
5046
type: "checkout",
5147
onClick() {
52-
trackEvent({
53-
category: "teamOnboarding",
54-
action: "selectPlan",
55-
label: "attempt",
48+
reportOnboardingPlanSelected({
5649
plan: "starter",
5750
});
5851
},
@@ -71,10 +64,7 @@ export function PlanSelector(props: {
7164
label: "Get Started",
7265
type: "checkout",
7366
onClick() {
74-
trackEvent({
75-
category: "teamOnboarding",
76-
action: "selectPlan",
77-
label: "attempt",
67+
reportOnboardingPlanSelected({
7868
plan: "growth",
7969
});
8070
},
@@ -94,10 +84,7 @@ export function PlanSelector(props: {
9484
label: "Get started",
9585
type: "checkout",
9686
onClick() {
97-
trackEvent({
98-
category: "teamOnboarding",
99-
action: "selectPlan",
100-
label: "attempt",
87+
reportOnboardingPlanSelected({
10188
plan: "scale",
10289
});
10390
},
@@ -116,10 +103,7 @@ export function PlanSelector(props: {
116103
label: "Get started",
117104
type: "checkout",
118105
onClick() {
119-
trackEvent({
120-
category: "teamOnboarding",
121-
action: "selectPlan",
122-
label: "attempt",
106+
reportOnboardingPlanSelected({
123107
plan: "pro",
124108
});
125109
},
@@ -147,11 +131,7 @@ export function PlanSelector(props: {
147131
className="self-center text-muted-foreground"
148132
asChild
149133
onClick={() => {
150-
trackEvent({
151-
category: "teamOnboarding",
152-
action: "selectPlan",
153-
label: "skip",
154-
});
134+
reportOnboardingPlanSelectionSkipped();
155135
}}
156136
>
157137
<Link

apps/dashboard/src/app/(app)/get-started/team/[team_slug]/select-plan/page.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ export default async function Page(props: {
1717
notFound();
1818
}
1919

20-
// const client = getClientThirdwebClient({
21-
// jwt: authToken,
22-
// teamId: team.id,
23-
// });
24-
2520
async function getTeam() {
2621
"use server";
2722
const resolvedTeam = await getTeamBySlug(params.team_slug);

0 commit comments

Comments
 (0)