Skip to content

chore(main): release 2.38.0#785

Open
releaser-wizard[bot] wants to merge 1 commit into
mainfrom
release-please--branches--main--components--wizard
Open

chore(main): release 2.38.0#785
releaser-wizard[bot] wants to merge 1 commit into
mainfrom
release-please--branches--main--components--wizard

Conversation

@releaser-wizard

@releaser-wizard releaser-wizard Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🤖 I have created a release beep boop

2.38.0 (2026-07-03)

Features

  • pi: real PostHog MCP dashboard, env lockdown, perf parity (#701) (9730dfd)
  • tui: Hidden Ctrl+T HUD showing running/final LLM token cost (#783) (b7ef4ba)

Bug Fixes

  • tui: disable crashing Visualizer tab (#787) (18bd38e)

This PR was generated with Release Please. See documentation.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci basic-integration
  • /wizard-ci error-tracking-upload-source-maps
  • /wizard-ci mcp-analytics
  • /wizard-ci misc
  • /wizard-ci revenue

Test an individual app:

  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
Show more apps
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci error-tracking-upload-source-maps/android
  • /wizard-ci error-tracking-upload-source-maps/cicd-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-nested-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-github-actions-single-stage-docker-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-gitlab-node-raw
  • /wizard-ci error-tracking-upload-source-maps/cicd-monorepo-pnpm-node-react
  • /wizard-ci error-tracking-upload-source-maps/cicd-monorepo-raw-node-react
  • /wizard-ci error-tracking-upload-source-maps/cicd-ssh-vps-node-raw
  • /wizard-ci error-tracking-upload-source-maps/flutter
  • /wizard-ci error-tracking-upload-source-maps/ios
  • /wizard-ci error-tracking-upload-source-maps/next
  • /wizard-ci error-tracking-upload-source-maps/next-no-posthog
  • /wizard-ci error-tracking-upload-source-maps/node-raw
  • /wizard-ci error-tracking-upload-source-maps/node-rollup
  • /wizard-ci error-tracking-upload-source-maps/node-rollup-typescript-plugin
  • /wizard-ci error-tracking-upload-source-maps/node-webpack
  • /wizard-ci error-tracking-upload-source-maps/nuxt-3-6
  • /wizard-ci error-tracking-upload-source-maps/nuxt-4-3
  • /wizard-ci error-tracking-upload-source-maps/react-native
  • /wizard-ci error-tracking-upload-source-maps/react-vite
  • /wizard-ci error-tracking-upload-source-maps/rust
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci misc/quack-quack
  • /wizard-ci revenue/stripe

Results will be posted here when complete.

@releaser-wizard releaser-wizard Bot force-pushed the release-please--branches--main--components--wizard branch from b67b152 to cb9b769 Compare July 2, 2026 03:45
@releaser-wizard releaser-wizard Bot force-pushed the release-please--branches--main--components--wizard branch from cb9b769 to 7188a5b Compare July 3, 2026 00:03
@gewenyu99

Copy link
Copy Markdown
Collaborator

/wizard-ci all

@wizard-ci-bot

wizard-ci-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🧙 Wizard CI Results

Trigger ID: 8dccaac
Workflow: View run

App Confidence PR YARA
audit/DemoHog3001 N/A Failed (logs)
audit/cal-diy N/A Failed (logs)
audit/dub N/A Failed (logs)
audit/posthog-demo-3000 N/A Failed (logs)
basic-integration/android/Jetchat 3/5 #2343 (logs)
basic-integration/angular/angular-saas 4/5 #2344 (logs)
basic-integration/astro/astro-hybrid-marketing 4/5 #2339 (logs)
basic-integration/astro/astro-ssr-docs 4/5 #2340 (logs)
basic-integration/astro/astro-static-marketing 5/5 #2341 (logs)
basic-integration/astro/astro-view-transitions-marketing 4/5 #2342 (logs)
basic-integration/django/django3-saas 4/5 #2345 (logs)
basic-integration/fastapi/fastapi3-ai-saas 5/5 #2354 (logs)
basic-integration/flask/flask3-social-media 4/5 #2352 (logs)
basic-integration/javascript-node/express-todo 4/5 #2347 (logs)
basic-integration/javascript-node/fastify-blog 4/5 #2346 (logs)
basic-integration/javascript-node/hono-links 4/5 #2349 (logs)
basic-integration/javascript-node/koa-notes 4/5 #2348 (logs)
basic-integration/javascript-node/native-http-contacts 4/5 #2350 (logs)
basic-integration/javascript-web/saas-dashboard 5/5 #2351 (logs)
basic-integration/laravel/laravel12-saas 3/5 #2353 (logs)
basic-integration/next-js/15-app-router-saas 4/5 #2356 (logs)
basic-integration/next-js/15-app-router-todo 4/5 #2355 (logs)
basic-integration/next-js/15-pages-router-saas 4/5 #2359 (logs)
basic-integration/next-js/15-pages-router-todo 4/5 #2357 (logs)
basic-integration/nuxt/movies-nuxt-3-6 4/5 #2361 (logs)
basic-integration/nuxt/movies-nuxt-4 4/5 #2358 (logs)
basic-integration/python/meeting-summarizer 4/5 #2360 (logs)
basic-integration/rails/fizzy 4/5 #2366 (logs) ⚠️
basic-integration/react-native/expo-react-native-hacker-news 4/5 #2363 (logs)
basic-integration/react-native/react-native-saas 4/5 #2362 (logs) ⚠️
basic-integration/react-router/react-router-v7-project 4/5 #2364 (logs) ⚠️
basic-integration/react-router/rrv7-starter 4/5 #2365 (logs)
basic-integration/react-router/saas-template 4/5 #2373 (logs)
basic-integration/react-router/shopper 4/5 #2367 (logs)
basic-integration/sveltekit/CMSaasStarter 4/5 #2370 (logs) ⚠️
basic-integration/swift/hackers-ios 5/5 #2375 (logs)
basic-integration/tanstack-router/tanstack-router-code-based-saas 4/5 #2369 (logs)
basic-integration/tanstack-router/tanstack-router-file-based-saas 4/5 #2372 (logs)
basic-integration/tanstack-start/tanstack-start-saas 4/5 #2377 (logs)
basic-integration/vue/movies 4/5 #2371 (logs)
mcp-analytics/custom-dispatcher/hono-server 5/5 #2368 (logs)
mcp-analytics/typescript-sdk/stdio-server 5/5 #2374 (logs)
migrate/triplex N/A Failed (logs)
revenue/stripe/stripe-next-js-saas-starter 3/5 #2376 (logs)
revenue/stripe/stripe-saas-demo 4/5 #2378 (logs)
self-driving/turbo-monorepo N/A Failed (logs)

Configuration

Setting Value
Wizard ref release-please--branches--main--components--wizard
Context Mill ref main
PostHog ref master

Search for trigger ID 8dccaac in wizard-workbench PRs.

⚠️ YARA Scanner — basic-integration/react-native/react-native-saas
45 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Write
⚠️ YARA Scanner — basic-integration/react-router/react-router-v7-project
65 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Edit
⚠️ YARA Scanner — basic-integration/rails/fizzy
95 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Edit
⚠️ YARA Scanner — basic-integration/sveltekit/CMSaasStarter
80 tool calls scanned, 1 violation(s) detected
[REVERTED] posthog_pii_in_capture_call (high) — PostToolUse:Edit

@gewenyu99

gewenyu99 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

🧪 Release test — 2.38.0 on a production repo: umami

umami — production web-analytics product, Next.js App Router. Ran the built 2.38.0 wizard (this release-please branch) locally, headless, snapshotting every screen. Evaluated the diff myself against the workbench rubric.

Run: 28 frames · runPhase: completed · intro → auth → run → outro → mcp → slack → keep-skills · diff 17 files, +122 / −3

Evaluation (workbench rubric)

Dimension Verdict
File analysis ✅ correct files & locations, clean additive code
PostHog implementation ⚠️ client+server init, 3-rule reverse proxy, identify (client and server), capture_exceptions all correct — but the SDK was never added to package.json
Event quality ✅ 12 real product events (website_created, team_joined, funnel_report_saved, server_login, …), enriched props (username, role), no PII dumps
App sanity won't build — imports posthog-js/posthog-node in 14 files, but neither dependency is in package.json

Confidence: 3/5. Thorough on a real codebase — identify on both sides, a correct 3-rule /ingest reverse proxy, 12 meaningful events, a written posthog-setup-report.md. Undercut by one build-breaking miss: imports the SDKs but never declares them, so pnpm install && next build fails. A 4/5 with the deps added.

📸 Snapshots — all 28 frames

01-intro

01-intro

02-auth

02-auth

03-run

03-run

04-run

04-run

05-run

05-run

06-run

06-run

07-run

07-run

08-run

08-run

09-run

09-run

10-run

10-run

11-run

11-run

12-run

12-run

13-run

13-run

14-run

14-run

15-run

15-run

16-run

16-run

17-run

17-run

18-run

18-run

19-run

19-run

20-run

20-run

21-run

21-run

22-run

22-run

23-run

23-run

24-run

24-run

25-outro

25-outro

26-mcp

26-mcp

27-slack-connect

27-slack-connect

28-keep-skills

28-keep-skills

🔧 Full diff (17 files, +122/−3)
diff --git a/.gitignore b/.gitignore
index 7ac75d6..c6713b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,3 +52,4 @@ yarn-error.log*
 *.env.*
 
 *.dev.yml
+.env.local
diff --git a/instrumentation-client.ts b/instrumentation-client.ts
new file mode 100644
index 0000000..b8ebf26
--- /dev/null
+++ b/instrumentation-client.ts
@@ -0,0 +1,9 @@
+import posthog from 'posthog-js';
+
+posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
+  api_host: '/ingest',
+  ui_host: 'https://us.posthog.com',
+  defaults: '2026-01-30',
+  capture_exceptions: true,
+  debug: process.env.NODE_ENV === 'development',
+});
diff --git a/next.config.ts b/next.config.ts
index 922d128..0218b68 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -246,6 +246,18 @@ export default withNextIntl({
   async rewrites() {
     return [
       ...rewrites,
+      {
+        source: '/ingest/static/:path*',
+        destination: 'https://us-assets.i.posthog.com/static/:path*',
+      },
+      {
+        source: '/ingest/array/:path*',
+        destination: 'https://us-assets.i.posthog.com/array/:path*',
+      },
+      {
+        source: '/ingest/:path*',
+        destination: 'https://us.i.posthog.com/:path*',
+      },
       {
         source: '/telemetry.js',
         destination: '/api/scripts/telemetry',
@@ -256,6 +268,7 @@ export default withNextIntl({
       },
     ];
   },
+  skipTrailingSlashRedirect: true,
   async redirects() {
     return [...redirects];
   },
diff --git a/posthog-setup-report.md b/posthog-setup-report.md
new file mode 100644
index 0000000..1c5c41a
--- /dev/null
+++ b/posthog-setup-report.md
@@ -0,0 +1,41 @@
+# PostHog post-wizard report
+
+The wizard has completed a deep integration of PostHog analytics into Umami. The setup covers client-side initialization via `instrumentation-client.ts` (Next.js 15.3+ pattern), a shared server-side PostHog client in `src/lib/posthog-server.ts`, a reverse proxy configured in `next.config.ts` to route PostHog traffic through `/ingest`, user identification on login (both client-side and server-side), and 12 instrumented events across key user flows.
+
+| Event | Description | File |
+|---|---|---|
+| `user_logged_in` | Fired when a user successfully logs in to Umami. | `src/app/login/LoginForm.tsx` |
+| `user_logged_out` | Fired when a user logs out of Umami. | `src/app/logout/LogoutPage.tsx` |
+| `website_created` | Fired when a user successfully adds a new website to track. | `src/app/(main)/websites/WebsiteAddForm.tsx` |
+| `website_deleted` | Fired when a user permanently deletes a tracked website. | `src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx` |
+| `website_updated` | Fired when a user saves changes to a website's settings. | `src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx` |
+| `website_reset` | Fired when a user resets all analytics data for a website. | `src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx` |
+| `team_created` | Fired when a user creates a new team. | `src/app/(main)/teams/TeamAddForm.tsx` |
+| `team_joined` | Fired when a user joins an existing team using an access code. | `src/app/(main)/teams/TeamJoinForm.tsx` |
+| `board_created` | Fired when a user creates a new custom dashboard board. | `src/app/(main)/boards/BoardEditForm.tsx` |
+| `password_changed` | Fired when a user successfully changes their account password. | `src/app/(main)/settings/profile/PasswordEditForm.tsx` |
+| `funnel_report_saved` | Fired when a user saves a funnel report configuration. | `src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx` |
+| `server_login` | Server-side event capturing each login with user identity on the authentication API. | `src/app/api/auth/login/route.ts` |
+
+## Next steps
+
+We've built some insights and a dashboard for you to keep an eye on user behavior, based on the events we just instrumented:
+
+- [Analytics basics (wizard) dashboard](https://us.posthog.com/project/228144/dashboard/1793612)
+- [Daily logins](https://us.posthog.com/project/228144/insights/XnyFVrzP)
+- [Websites created vs deleted](https://us.posthog.com/project/228144/insights/wHP9LAcM)
+- [Team activity — created & joined](https://us.posthog.com/project/228144/insights/GPcKhHwU)
+- [Feature adoption — boards & funnel reports](https://us.posthog.com/project/228144/insights/JLqLxknE)
+- [Website churn rate (deleted / created)](https://us.posthog.com/project/228144/insights/2RqYjMyh)
+
+## Verify before merging
+
+- [ ] Run a full production build (the wizard only verified the files it touched) and fix any lint or type errors introduced by the generated code.
+- [ ] Run the test suite — call sites that were rewritten or instrumented may need updated mocks or fixtures.
+- [ ] Add `NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN` and `NEXT_PUBLIC_POSTHOG_HOST` to `.env.example` and any monorepo/bootstrap scripts so collaborators know what to set.
+- [ ] Wire source-map upload (`posthog-cli sourcemap` or your bundler's upload step) into CI so production stack traces de-minify.
+- [ ] Confirm the returning-visitor path also calls `identify` — `LoginForm.tsx` identifies on fresh login, but returning sessions that skip login (token still valid) will remain on anonymous distinct IDs until the next explicit login.
+
+### Agent skill
+
+We've left an agent skill folder in your project. You can use this context for further agent development when using Claude Code. This will help ensure the model provides the most up-to-date approaches for integrating PostHog.
diff --git a/src/app/(main)/boards/BoardEditForm.tsx b/src/app/(main)/boards/BoardEditForm.tsx
index ec405ff..bba62aa 100644
--- a/src/app/(main)/boards/BoardEditForm.tsx
+++ b/src/app/(main)/boards/BoardEditForm.tsx
@@ -10,6 +10,7 @@ import {
   Select,
   TextField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useBoardQuery, useMessages, useNavigation, useUpdateQuery } from '@/components/hooks';
 import { LinkSelect } from '@/components/input/LinkSelect';
 import { PixelSelect } from '@/components/input/PixelSelect';
@@ -75,6 +76,9 @@ export function BoardEditForm({
       parameters: setBoardEntity(board?.parameters, data.type, data.entityId || undefined),
     });
 
+    if (!boardId) {
+      posthog.capture('board_created', { board_id: result.id, board_name: data.name, board_type: data.type });
+    }
     toast(t(messages.saved));
     touch('boards');
     touch(`board:${result.id}`);
diff --git a/src/app/(main)/settings/profile/PasswordEditForm.tsx b/src/app/(main)/settings/profile/PasswordEditForm.tsx
index 8a1615a..2acf070 100644
--- a/src/app/(main)/settings/profile/PasswordEditForm.tsx
+++ b/src/app/(main)/settings/profile/PasswordEditForm.tsx
@@ -6,6 +6,7 @@ import {
   FormSubmitButton,
   PasswordField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 
 export function PasswordEditForm({ onSave, onClose }) {
@@ -15,6 +16,7 @@ export function PasswordEditForm({ onSave, onClose }) {
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
       onSuccess: async () => {
+        posthog.capture('password_changed');
         onSave();
         onClose();
       },
diff --git a/src/app/(main)/teams/TeamAddForm.tsx b/src/app/(main)/teams/TeamAddForm.tsx
index 2599baa..9b904c1 100644
--- a/src/app/(main)/teams/TeamAddForm.tsx
+++ b/src/app/(main)/teams/TeamAddForm.tsx
@@ -6,6 +6,7 @@ import {
   FormSubmitButton,
   TextField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 import { UserSelect } from '@/components/input/UserSelect';
 
@@ -23,7 +24,8 @@ export function TeamAddForm({
 
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
-      onSuccess: async () => {
+      onSuccess: async (team: any) => {
+        posthog.capture('team_created', { team_name: data.name, team_id: team?.id });
         onSave?.();
         onClose?.();
       },
diff --git a/src/app/(main)/teams/TeamJoinForm.tsx b/src/app/(main)/teams/TeamJoinForm.tsx
index 3aa8653..a0487c4 100644
--- a/src/app/(main)/teams/TeamJoinForm.tsx
+++ b/src/app/(main)/teams/TeamJoinForm.tsx
@@ -6,6 +6,7 @@ import {
   FormSubmitButton,
   TextField,
 } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 
 export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
@@ -14,7 +15,8 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose:
 
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
-      onSuccess: async () => {
+      onSuccess: async (team: any) => {
+        posthog.capture('team_joined', { team_id: team?.id, team_name: team?.name });
         touch('teams:members');
         onSave?.();
         onClose?.();
diff --git a/src/app/(main)/websites/WebsiteAddForm.tsx b/src/app/(main)/websites/WebsiteAddForm.tsx
index 7f28651..0b23c1f 100644
--- a/src/app/(main)/websites/WebsiteAddForm.tsx
+++ b/src/app/(main)/websites/WebsiteAddForm.tsx
@@ -1,4 +1,5 @@
 import { Button, Form, FormField, FormSubmitButton, Row, TextField } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 import { DOMAIN_REGEX } from '@/lib/constants';
 
@@ -16,7 +17,13 @@ export function WebsiteAddForm({
 
   const handleSubmit = async (data: any) => {
     await mutateAsync(data, {
-      onSuccess: async () => {
+      onSuccess: async (website: any) => {
+        posthog.capture('website_created', {
+          website_name: data.name,
+          website_domain: data.domain,
+          website_id: website?.id,
+          team_id: teamId,
+        });
         onSave?.();
         onClose?.();
       },
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
index 99cc52b..6fc15be 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
@@ -15,6 +15,7 @@ import {
   TextField,
 } from '@umami/react-zen';
 import { Fragment, useState } from 'react';
+import posthog from 'posthog-js';
 import { useApi, useMessages, useMobile, useReportQuery, useUpdateQuery } from '@/components/hooks';
 import { Plus, X } from '@/components/icons';
 import { ActionSelect } from '@/components/input/ActionSelect';
@@ -173,6 +174,12 @@ export function FunnelEditForm({
       { ...data, id, name, type: 'funnel', websiteId, parameters },
       {
         onSuccess: async () => {
+          posthog.capture('funnel_report_saved', {
+            report_name: name,
+            website_id: websiteId,
+            is_new: !id,
+            step_count: (parameters as any)?.steps?.length,
+          });
           touch('reports:funnel');
           touch(`report:${id}`);
           onSave?.();
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx
index fb450f4..6848d89 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteDeleteForm.tsx
@@ -1,3 +1,4 @@
+import posthog from 'posthog-js';
 import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm';
 import { useDeleteQuery, useMessages } from '@/components/hooks';
 
@@ -18,6 +19,7 @@ export function WebsiteDeleteForm({
   const handleConfirm = async () => {
     await mutateAsync(null, {
       onSuccess: async () => {
+        posthog.capture('website_deleted', { website_id: websiteId });
         touch('websites');
         touch(`websites:${websiteId}`);
         onSave?.();
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
index 63d3703..0231bb4 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteEditForm.tsx
@@ -1,4 +1,5 @@
 import { Form, FormButtons, FormField, FormSubmitButton, TextField } from '@umami/react-zen';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery, useWebsite } from '@/components/hooks';
 import { DOMAIN_REGEX } from '@/lib/constants';
 
@@ -11,6 +12,11 @@ export function WebsiteEditForm({ websiteId, onSave }: { websiteId: string; onSa
     const { shareId, ...updateData } = data;
     await mutateAsync(updateData, {
       onSuccess: async () => {
+        posthog.capture('website_updated', {
+          website_id: websiteId,
+          website_name: updateData.name,
+          website_domain: updateData.domain,
+        });
         toast(t(messages.saved));
         touch('websites');
         touch(`website:${website.id}`);
diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx
index 72a308e..1c915af 100644
--- a/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteResetForm.tsx
@@ -1,3 +1,4 @@
+import posthog from 'posthog-js';
 import { TypeConfirmationForm } from '@/components/common/TypeConfirmationForm';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 
@@ -18,6 +19,7 @@ export function WebsiteResetForm({
   const handleConfirm = async () => {
     await mutateAsync(null, {
       onSuccess: async () => {
+        posthog.capture('website_reset', { website_id: websiteId });
         onSave?.();
         onClose?.();
       },
diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts
index 9b2ad94..03c4326 100644
--- a/src/app/api/auth/login/route.ts
+++ b/src/app/api/auth/login/route.ts
@@ -7,6 +7,7 @@ import { checkPassword } from '@/lib/password';
 import redis from '@/lib/redis';
 import { parseRequest } from '@/lib/request';
 import { json, unauthorized } from '@/lib/response';
+import { getPostHogClient } from '@/lib/posthog-server';
 import { getAllUserTeams, getUserByUsername } from '@/queries/prisma';
 
 export async function POST(request: Request) {
@@ -44,6 +45,11 @@ export async function POST(request: Request) {
 
   const teams = await getAllUserTeams(id);
 
+  const posthog = getPostHogClient();
+  posthog.identify({ distinctId: id, properties: { username, role } });
+  posthog.capture({ distinctId: id, event: 'server_login', properties: { username, role } });
+  await posthog.shutdown();
+
   return json({
     token,
     user: { id, username, role, createdAt, isAdmin: role === ROLES.admin, teams },
diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx
index 23101b9..279bb7d 100644
--- a/src/app/login/LoginForm.tsx
+++ b/src/app/login/LoginForm.tsx
@@ -10,6 +10,7 @@ import {
   TextField,
 } from '@umami/react-zen';
 import { useRouter } from 'next/navigation';
+import posthog from 'posthog-js';
 import { useMessages, useUpdateQuery } from '@/components/hooks';
 import { Logo } from '@/components/svg';
 import { setClientAuthToken } from '@/lib/client';
@@ -25,6 +26,8 @@ export function LoginForm() {
       onSuccess: async ({ token, user }) => {
         setClientAuthToken(token);
         setUser(user);
+        posthog.identify(user.id, { username: user.username, role: user.role });
+        posthog.capture('user_logged_in', { username: user.username, role: user.role });
         router.push('/');
       },
     });
diff --git a/src/app/logout/LogoutPage.tsx b/src/app/logout/LogoutPage.tsx
index 33e1615..3604b32 100644
--- a/src/app/logout/LogoutPage.tsx
+++ b/src/app/logout/LogoutPage.tsx
@@ -1,6 +1,7 @@
 'use client';
 import { useRouter } from 'next/navigation';
 import { useEffect } from 'react';
+import posthog from 'posthog-js';
 import { useApi } from '@/components/hooks';
 import { removeClientAuthToken } from '@/lib/client';
 import { setUser } from '@/store/app';
@@ -18,6 +19,8 @@ export function LogoutPage() {
 
     removeClientAuthToken();
     setUser(null);
+    posthog.capture('user_logged_out');
+    posthog.reset();
     logout();
   }, [router, post]);
 
diff --git a/src/lib/posthog-server.ts b/src/lib/posthog-server.ts
new file mode 100644
index 0000000..d93e269
--- /dev/null
+++ b/src/lib/posthog-server.ts
@@ -0,0 +1,9 @@
+import { PostHog } from 'posthog-node';
+
+export function getPostHogClient() {
+  return new PostHog(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
+    host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
+    flushAt: 1,
+    flushInterval: 0,
+  });
+}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant