Skip to content

feat: custom analytics events for signup, checkout, and report views - #1907

Open
sweetmantech wants to merge 1 commit into
mainfrom
feat/1902-analytics-events
Open

feat: custom analytics events for signup, checkout, and report views#1907
sweetmantech wants to merge 1 commit into
mainfrom
feat/1902-analytics-events

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Part of #1902 (C5 — web UX conversion audit).

chat.recoupable.dev has @vercel/analytics installed (<Analytics/> in app/layout.tsx) but emitted zero custom events, so the signup/checkout funnel was unmeasurable. This PR adds a thin lib/analytics/trackEvent.ts wrapper (single door for all future events; forwards name + props to @vercel/analytics track; never throws; no PII in props) and instruments four funnel events.

Events

Event Trigger Props
signup_started Immediately before every programmatic Privy login() call, via the trackSignupStarted(source) helper source: menu (sidebar/menu action gate in useUser.isPrepared), modal_auto (useAutoLogin auto-prompt), signin_page (/signin), start_button (shared StartButton)
signup_completed Privy useLogin onComplete (mounted under UserProvider); skipped when wasAlreadyAuthenticated (session restore) is_new_user (boolean), login_method (string)
checkout_opened Stripe checkout session URL received in lib/stripe/createClientCheckoutSession.ts, just before the checkout tab opens none
valuation_report_viewed Catalog report page mount (components/Catalog/report/CatalogReportPage.tsx, serving /catalogs/[catalogId]) catalog_id

No PII (emails, names, wallet addresses) is included in any event props.

Files

  • lib/analytics/trackEvent.ts — new thin wrapper (one exported function)
  • lib/analytics/trackSignupStarted.ts — new signup_started helper with typed source
  • lib/analytics/__tests__/trackEvent.test.ts — new unit tests (TDD, red then green)
  • hooks/useTrackSignupCompleted.tsx — new hook on Privy useLogin onComplete
  • providers/UserProvder.tsx — mounts the completion hook next to auto-login
  • hooks/useAutoLogin.tsx, components/SignInPage/SignInPage.tsx, components/shared/StartButton.tsx, hooks/useUser.tsx — one trackSignupStarted(...) line before each login()
  • lib/stripe/createClientCheckoutSession.ts — one additive trackEvent("checkout_opened") line (kept minimal; sibling PRs touch this file)
  • components/Catalog/report/CatalogReportPage.tsx — mount effect firing valuation_report_viewed

Testing

  • pnpm exec vitest run — 75 files / 279 tests pass (includes the 3 new trackEvent tests: forwards name+props, forwards name alone, never throws when track throws)
  • pnpm exec tsc --noEmit — no errors in changed files (8 pre-existing baseline errors in SpotifyDeepResearchResult.tsx / extractSendEmailResults.test.ts, untouched by this PR)

How to verify in the Vercel Web Analytics dashboard

  1. Open the Vercel dashboard → chat project → AnalyticsEvents tab (custom events).
  2. On the preview deployment, walk the funnel: land signed-out (fires signup_started with source=modal_auto), complete the Privy email login (signup_completed), open a catalog report at /catalogs/[catalogId] (valuation_report_viewed with the catalog_id prop), and click Subscribe as a non-subscribed user (checkout_opened).
  3. Each event should appear in the Events panel within a few minutes; use the property filters to break signup_started down by source.

Note: custom events require the Web Analytics Plus/Pro events feature to be enabled on the project; events fired from preview deployments show under that deployment's environment filter.

Preview verification to follow in a PR comment.

🤖 Generated with Claude Code


Summary by cubic

Adds custom analytics events using @vercel/analytics to track signup, checkout, and report views. Supports #1902 conversion audit by making the funnel measurable.

  • New Features
    • Wrapper trackEvent for @vercel/analytics (single entry point, never throws, no PII).
    • Events: signup_started (sources: menu, modal_auto, signin_page, start_button), signup_completed (props: is_new_user, login_method), checkout_opened, valuation_report_viewed (prop: catalog_id).

Written for commit bcc854b. Summary will update on new commits.

Review in cubic

chat.recoupable.dev had @vercel/analytics installed but emitted zero
custom events, so the signup/checkout funnel was unmeasurable
(#1902 C5). Adds a thin trackEvent wrapper (single door,
never throws, no PII) and four funnel events:

- signup_started (source: menu | modal_auto | signin_page | start_button)
  fired via trackSignupStarted before every programmatic Privy login()
- signup_completed (is_new_user, login_method) via Privy useLogin
  onComplete, skipping already-authenticated session restores
- checkout_opened when a Stripe checkout session URL is received
- valuation_report_viewed (catalog_id) on catalog report page mount

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview Jul 29, 2026 2:58pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bb965264-28be-4e9f-95af-81fb0acd3f2d

📥 Commits

Reviewing files that changed from the base of the PR and between d0913d1 and bcc854b.

⛔ Files ignored due to path filters (1)
  • lib/analytics/__tests__/trackEvent.test.ts is excluded by !**/*.test.* and included by lib/**
📒 Files selected for processing (10)
  • components/Catalog/report/CatalogReportPage.tsx
  • components/SignInPage/SignInPage.tsx
  • components/shared/StartButton.tsx
  • hooks/useAutoLogin.tsx
  • hooks/useTrackSignupCompleted.tsx
  • hooks/useUser.tsx
  • lib/analytics/trackEvent.ts
  • lib/analytics/trackSignupStarted.ts
  • lib/stripe/createClientCheckoutSession.ts
  • providers/UserProvder.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bcc854b9b9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return;
}

trackSignupStarted("signin_page");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid double-counting sign-in starts

When an anonymous user lands on /signin, app/layout.tsx still wraps that page in Providers -> UserProvider, so UserAutoLogin runs useAutoLogin and already fires signup_started with source=modal_auto before its login(). This added signin_page event runs in the same mount for the same Privy login flow, so /signin sessions produce two signup_started events and inflate the funnel/source split; gate auto-login off the signin route or choose one source.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 11 files

Confidence score: 3/5

  • In hooks/useTrackSignupCompleted.tsx, signup_completed is wired to useLogin({ onComplete }) but the callback only runs for that hook instance’s scoped login() call, so new signups may never emit completion and conversion funnels will be undercounted — trigger the event from the signup success path that actually executes for new registrations (or share the same login() invocation path).
  • In components/SignInPage/SignInPage.tsx, signup_started can fire on /signin while app-wide UserAutoLogin also emits signup_started in the same mount cycle, which can double-count starts and skew source attribution (signin_page vs modal_auto) — add a dedupe/guard so only one source event is sent per session/flow.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/SignInPage/SignInPage.tsx">

<violation number="1" location="components/SignInPage/SignInPage.tsx:19">
P2: On the /signin route, this call fires signup_started with source=signin_page in the same mount where the app-wide UserAutoLogin (mounted via UserProvider) already fires signup_started with source=modal_auto before calling login(). This double-counts signup starts for /signin sessions and skews the funnel/source breakdown. Consider gating auto-login off this route or picking a single source of truth for the event.</violation>
</file>

<file name="hooks/useTrackSignupCompleted.tsx">

<violation number="1" location="hooks/useTrackSignupCompleted.tsx:12">
P1: The `signup_completed` custom event will never fire for new signups. The `useLogin({ onComplete })` hook creates a scoped `login()` function, and the `onComplete` callback fires only when that specific `login()` is called. Since the returned `login()` is never invoked anywhere — all logins use `usePrivy().login()` instead — the callback never executes for actual authentication flows. To fix this, either use `usePrivy` with a `useEffect` that watches the `user` or `authenticated` state and fires the event once when it transitions from unauthenticated to authenticated, or wire the `onComplete` through each component that calls `login()`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

* No PII in props (chat#1902 C5).
*/
export function useTrackSignupCompleted() {
useLogin({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The signup_completed custom event will never fire for new signups. The useLogin({ onComplete }) hook creates a scoped login() function, and the onComplete callback fires only when that specific login() is called. Since the returned login() is never invoked anywhere — all logins use usePrivy().login() instead — the callback never executes for actual authentication flows. To fix this, either use usePrivy with a useEffect that watches the user or authenticated state and fires the event once when it transitions from unauthenticated to authenticated, or wire the onComplete through each component that calls login().

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useTrackSignupCompleted.tsx, line 12:

<comment>The `signup_completed` custom event will never fire for new signups. The `useLogin({ onComplete })` hook creates a scoped `login()` function, and the `onComplete` callback fires only when that specific `login()` is called. Since the returned `login()` is never invoked anywhere — all logins use `usePrivy().login()` instead — the callback never executes for actual authentication flows. To fix this, either use `usePrivy` with a `useEffect` that watches the `user` or `authenticated` state and fires the event once when it transitions from unauthenticated to authenticated, or wire the `onComplete` through each component that calls `login()`.</comment>

<file context>
@@ -0,0 +1,23 @@
+ * No PII in props (chat#1902 C5).
+ */
+export function useTrackSignupCompleted() {
+  useLogin({
+    onComplete: ({ isNewUser, wasAlreadyAuthenticated, loginMethod }) => {
+      if (wasAlreadyAuthenticated) return;
</file context>

return;
}

trackSignupStarted("signin_page");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: On the /signin route, this call fires signup_started with source=signin_page in the same mount where the app-wide UserAutoLogin (mounted via UserProvider) already fires signup_started with source=modal_auto before calling login(). This double-counts signup starts for /signin sessions and skews the funnel/source breakdown. Consider gating auto-login off this route or picking a single source of truth for the event.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/SignInPage/SignInPage.tsx, line 19:

<comment>On the /signin route, this call fires signup_started with source=signin_page in the same mount where the app-wide UserAutoLogin (mounted via UserProvider) already fires signup_started with source=modal_auto before calling login(). This double-counts signup starts for /signin sessions and skews the funnel/source breakdown. Consider gating auto-login off this route or picking a single source of truth for the event.</comment>

<file context>
@@ -15,6 +16,7 @@ export default function SignInPage() {
       return;
     }
 
+    trackSignupStarted("signin_page");
     login();
     // eslint-disable-next-line
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant