Skip to content

Added mixpanel to app to monitor what is acutally being used by how much#2078

Merged
GelatoGenesis merged 3 commits intomainfrom
b-17728063203
Mar 6, 2026
Merged

Added mixpanel to app to monitor what is acutally being used by how much#2078
GelatoGenesis merged 3 commits intomainfrom
b-17728063203

Conversation

@GelatoGenesis
Copy link
Copy Markdown
Collaborator

@GelatoGenesis GelatoGenesis commented Mar 6, 2026

Summary by CodeRabbit

  • New Features

    • Optional Mixpanel analytics enabled for production builds; tracks page views and (with consent) identifies connected user profiles.
    • Cookie consent gains a performance-consent option so users can opt in/out of performance analytics; revoking consent stops tracking and clears analytics state.
  • Tests

    • Added comprehensive tests covering analytics initialization, page tracking, identification, consent scenarios, identity clearing, and reset behavior.

Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

Adds Mixpanel client analytics: a frontend Mixpanel service, a MixpanelSetup React component wired into Providers that respects cookie consent and route changes, new unit tests, an env schema entry and CI exposure for NEXT_PUBLIC_MIXPANEL_TOKEN, and the mixpanel-browser dependency.

Changes

Cohort / File(s) Summary
Analytics service
services/analytics/mixpanel.ts
New frontend Mixpanel wrapper exporting initAnalytics, trackPageView, identify, clearIdentity, and disableAnalytics; production-only activation via NEXT_PUBLIC_MIXPANEL_TOKEN, idempotent init/identify, property sanitization, and readiness guards.
Provider & integration
components/providers/MixpanelSetup.tsx, components/providers/Providers.tsx
New client component MixpanelSetup added and rendered from Providers; manages init/identify/track flows based on performanceConsent, pathname and connected profile, using refs to avoid duplicate actions and returning null (no UI).
Cookie consent context
components/cookies/CookieConsentContext.tsx
Adds performanceConsent to the exported context, consolidates cookie consent type, introduces a ref-based one-time load guard for performance scripts, and switches toast access to useAuth().
Tests
__tests__/components/providers/MixpanelSetup.test.tsx, __tests__/services/analytics/mixpanel.test.ts
New unit tests covering consent gating, init/reset behavior, page tracking, identification/idempotency, and no-op behavior when not in production or token missing.
Config, deps & CI
config/env.schema.ts, package.json, .github/workflows/.../build-upload-deploy-prod.yml
Adds NEXT_PUBLIC_MIXPANEL_TOKEN to public env schema, adds mixpanel-browser dependency, and exposes the token in the production workflow environment.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Router as Next.js Router
    participant CookieConsent as CookieConsentContext
    participant MixpanelSetup
    participant Analytics as Mixpanel Service
    participant Mixpanel

    User->>Router: navigate (pathname change)
    Router->>MixpanelSetup: pathname update
    MixpanelSetup->>CookieConsent: read performanceConsent

    alt performanceConsent = true
        MixpanelSetup->>Analytics: initAnalytics() [if not initialized]
        Analytics->>Mixpanel: initialize with token & options
        Mixpanel-->>Analytics: ready

        MixpanelSetup->>Analytics: trackPageView(path, {has_connected_profile})
        Analytics->>Mixpanel: track("Page Viewed", properties)

        alt connectedProfile exists
            MixpanelSetup->>Analytics: identify(profileId, traits)
            Analytics->>Mixpanel: set distinct_id & update people
        end
    else performanceConsent = false
        MixpanelSetup->>Analytics: disableAnalytics() / clearIdentity()
        Analytics->>Mixpanel: clear state
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • analyticsflowee
  • prxt6529
  • simo6529

Poem

🐰 I hopped through routes both near and far,
I checked the cookies, found the jar,
With consent I count each little track,
Withdraw a nod — I tidy back.
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the PR: adding Mixpanel analytics to monitor application usage metrics.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch b-17728063203

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 and usage tips.

Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/cookies/CookieConsentContext.tsx (1)

78-103: ⚠️ Potential issue | 🟠 Major

Revoking performance consent still leaves the GA runtime active.

Line 83 only prevents duplicate injection; it does not disable the already-loaded analytics runtime when consent later becomes false. After one accept, the current tab can keep sending performance data until reload, which breaks consent withdrawal semantics. Please add a symmetric disable/teardown path when performanceConsent is revoked.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/cookies/CookieConsentContext.tsx` around lines 78 - 103, Add a
teardown path that reverses what loadPerformanceCookies does when
performanceConsent becomes false: when performanceConsent transitions to false
(watch the performanceConsent state), remove the injected script elements (the
gtag script and the inline script added in loadPerformanceCookies), clear and/or
delete window.dataLayer and the global gtag function, and reset
performanceScriptsLoadedRef.current so cookies can be reloaded later; also
invoke the GTM/gtag consent API (e.g., update analytics_storage to "denied") if
available before removing scripts to stop further sends. Ensure this logic
references performanceConsent, loadPerformanceCookies, and
performanceScriptsLoadedRef so it runs on consent revocation and cleanly
disables the GA runtime.
🧹 Nitpick comments (1)
services/analytics/mixpanel.ts (1)

40-53: Consider wrapping Mixpanel calls in try-catch for resilience.

If mixpanel.init throws (e.g., due to an ad blocker or browser extension interference), the error will propagate and could disrupt the application. Analytics failures should not affect core functionality.

♻️ Proposed defensive wrapper
 export const initAnalytics = (): boolean => {
   const token = MIXPANEL_TOKEN;
   if (!isAnalyticsEnabled() || hasInitialized || !token) {
     return false;
   }

+  try {
     mixpanel.init(token, {
       autocapture: false,
       persistence: "localStorage",
       track_pageview: false,
     });
     hasInitialized = true;
     return true;
+  } catch {
+    return false;
+  }
 };

Similar pattern could be applied to track, identify, and reset functions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/analytics/mixpanel.ts` around lines 40 - 53, The initAnalytics
function currently calls mixpanel.init directly and can throw; wrap the
mixpanel.init call in a try-catch inside initAnalytics, set/leave hasInitialized
only on success, return false on error, and log the caught error (so analytics
failures don't bubble up). Apply the same defensive pattern (try-catch, no-ops
or safe returns, and logging) to the other Mixpanel wrappers: track, identify,
and reset to ensure any mixpanel.* exceptions are contained.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/providers/MixpanelSetup.tsx`:
- Around line 33-47: The page-view useEffect (referencing lastTrackedPathRef and
trackPageView) can fire before the user is identified, causing the first "Page
Viewed" to be anonymous; update the effect so that when
lastTrackedPathRef.current !== pathname and hasConsent is true you first call
your identification helper (e.g., identify or identifyUser with
connectedProfile?.id and profile traits) before calling trackPageView, or
alternatively move the identify logic ahead of this effect; ensure you only
identify once per session (guarded by connectedProfile?.id and a flag/ref) and
then set lastTrackedPathRef.current = pathname and call trackPageView so the
initial page event is attributed to the logged-in profile.

---

Outside diff comments:
In `@components/cookies/CookieConsentContext.tsx`:
- Around line 78-103: Add a teardown path that reverses what
loadPerformanceCookies does when performanceConsent becomes false: when
performanceConsent transitions to false (watch the performanceConsent state),
remove the injected script elements (the gtag script and the inline script added
in loadPerformanceCookies), clear and/or delete window.dataLayer and the global
gtag function, and reset performanceScriptsLoadedRef.current so cookies can be
reloaded later; also invoke the GTM/gtag consent API (e.g., update
analytics_storage to "denied") if available before removing scripts to stop
further sends. Ensure this logic references performanceConsent,
loadPerformanceCookies, and performanceScriptsLoadedRef so it runs on consent
revocation and cleanly disables the GA runtime.

---

Nitpick comments:
In `@services/analytics/mixpanel.ts`:
- Around line 40-53: The initAnalytics function currently calls mixpanel.init
directly and can throw; wrap the mixpanel.init call in a try-catch inside
initAnalytics, set/leave hasInitialized only on success, return false on error,
and log the caught error (so analytics failures don't bubble up). Apply the same
defensive pattern (try-catch, no-ops or safe returns, and logging) to the other
Mixpanel wrappers: track, identify, and reset to ensure any mixpanel.*
exceptions are contained.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d051dc0f-8c4b-4777-b3da-32a84d3966d4

📥 Commits

Reviewing files that changed from the base of the PR and between b57a939 and bc432bc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • .github/workflows/build-upload-deploy-prod.yml
  • __tests__/components/providers/MixpanelSetup.test.tsx
  • __tests__/services/analytics/mixpanel.test.ts
  • components/cookies/CookieConsentContext.tsx
  • components/providers/MixpanelSetup.tsx
  • components/providers/Providers.tsx
  • config/env.schema.ts
  • package.json
  • services/analytics/mixpanel.ts

Comment thread components/providers/MixpanelSetup.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/analytics/mixpanel.ts`:
- Around line 97-100: The track call currently spreads properties after the
explicit path which allows properties.path to override the function argument;
update the object so the function argument takes precedence by placing the
spread before the path (or by removing/ignoring properties.path and then setting
path to the function argument) in the track("Page Viewed", {...}) call to ensure
the argument `path` cannot be overwritten by `properties.path`.
- Around line 83-91: The reset() function currently clears identifiedDistinctId
and calls mixpanel.reset() but leaves hasInitialized true so isAnalyticsReady()
still returns true; update reset() to also set hasInitialized = false (after
calling mixpanel.reset()) so future calls to
isAnalyticsReady()/track()/identify() will be blocked until re-initialization;
ensure you reference the hasInitialized flag and the isAnalyticsReady(),
reset(), mixpanel.reset(), track(), and identify() symbols when making the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 809db27a-2e22-4e70-ac46-85696bebd1a8

📥 Commits

Reviewing files that changed from the base of the PR and between bc432bc and 6d4f13f.

📒 Files selected for processing (1)
  • services/analytics/mixpanel.ts

Comment thread services/analytics/mixpanel.ts Outdated
Comment thread services/analytics/mixpanel.ts
Signed-off-by: GelatoGenesis <tarmokalling@gmail.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Mar 6, 2026

@GelatoGenesis
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
components/providers/MixpanelSetup.tsx (1)

23-32: ⚠️ Potential issue | 🟠 Major

Premature disableAnalytics() call on mount when consent is still loading.

Per context snippet 4, performanceConsent initializes to undefined in CookieConsentContext. On first render, hasConsent evaluates to false, triggering disableAnalytics() before the actual consent value loads from the cookie. This sets isTrackingAllowed = false and calls mixpanel.reset(), potentially suppressing analytics for users who already granted consent until the next initAnalytics() call.

Consider distinguishing between "consent denied" (performanceConsent === false) and "consent not yet loaded" (performanceConsent === undefined):

Proposed fix
   const hasConsent = performanceConsent === true;
+  const consentLoaded = performanceConsent !== undefined;

   useEffect(() => {
-    if (!hasConsent) {
+    if (!consentLoaded) {
+      return; // Wait for consent state to load
+    }
+
+    if (!hasConsent) {
       disableAnalytics();
       lastTrackedPathRef.current = null;
       identifiedProfileIdRef.current = null;
       return;
     }

     initAnalytics();
-  }, [hasConsent]);
+  }, [hasConsent, consentLoaded]);
🧹 Nitpick comments (2)
__tests__/components/providers/MixpanelSetup.test.tsx (2)

11-11: Minor type inconsistency in mock.

The mock type { id: number } differs from the actual ApiIdentity.id type which is string | null (per generated/models/ApiIdentity.ts). While the test still validates correct behavior since the component converts to string, consider aligning the mock type for clarity:

Suggested change
-let connectedProfile: { id: number } | null = null;
+let connectedProfile: { id: string } | null = null;

And update test values:

-    connectedProfile = { id: 42 };
+    connectedProfile = { id: "42" };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/providers/MixpanelSetup.test.tsx` at line 11, The test
mock variable connectedProfile currently types id as number; change its type to
match ApiIdentity.id (string | null) by typing connectedProfile as { id: string
| null } | null (or reuse ApiIdentity type) and update any numeric test values
to string values (or "null" cases) so test data aligns with
generated/models/ApiIdentity.ts; ensure tests still exercise the component's
conversion logic where it casts id to string.

51-58: Consider adding a test for consent loading state.

This test verifies disableAnalytics is called when performanceConsent is undefined, which validates current behavior. However, if the consent-loading race condition is fixed (distinguishing undefined from false), this test would need updating. Consider adding a test case for the transition from undefinedtrue to verify analytics initializes correctly after consent loads.

Example additional test
it("initializes analytics when consent transitions from loading to granted", () => {
  performanceConsent = undefined;
  const { rerender } = render(<MixpanelSetup />);

  // Should not initialize while loading
  expect(initAnalyticsMock).not.toHaveBeenCalled();

  performanceConsent = true;
  pathname = "/waves";
  rerender(<MixpanelSetup />);

  expect(initAnalyticsMock).toHaveBeenCalledTimes(1);
  expect(trackPageViewMock).toHaveBeenCalledWith("/waves", {
    has_connected_profile: false,
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@__tests__/components/providers/MixpanelSetup.test.tsx` around lines 51 - 58,
Add a test in the MixpanelSetup suite that simulates the consent-loading
transition: render <MixpanelSetup /> with the mocked performanceConsent
initially set to undefined and assert initAnalyticsMock and trackPageViewMock
have not been called, then set performanceConsent = true, set pathname (e.g.,
"/waves"), call rerender(<MixpanelSetup />) and assert initAnalyticsMock was
called once and trackPageViewMock was called with the new pathname and the
expected props (e.g., { has_connected_profile: false }); reference the existing
mocks initAnalyticsMock, trackPageViewMock, identifyMock, disableAnalyticsMock
and use rerender to trigger the component update.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@__tests__/components/providers/MixpanelSetup.test.tsx`:
- Line 11: The test mock variable connectedProfile currently types id as number;
change its type to match ApiIdentity.id (string | null) by typing
connectedProfile as { id: string | null } | null (or reuse ApiIdentity type) and
update any numeric test values to string values (or "null" cases) so test data
aligns with generated/models/ApiIdentity.ts; ensure tests still exercise the
component's conversion logic where it casts id to string.
- Around line 51-58: Add a test in the MixpanelSetup suite that simulates the
consent-loading transition: render <MixpanelSetup /> with the mocked
performanceConsent initially set to undefined and assert initAnalyticsMock and
trackPageViewMock have not been called, then set performanceConsent = true, set
pathname (e.g., "/waves"), call rerender(<MixpanelSetup />) and assert
initAnalyticsMock was called once and trackPageViewMock was called with the new
pathname and the expected props (e.g., { has_connected_profile: false });
reference the existing mocks initAnalyticsMock, trackPageViewMock, identifyMock,
disableAnalyticsMock and use rerender to trigger the component update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d167f036-3170-443b-bc65-1a3f7a424f81

📥 Commits

Reviewing files that changed from the base of the PR and between 6d4f13f and ea3fc78.

📒 Files selected for processing (4)
  • __tests__/components/providers/MixpanelSetup.test.tsx
  • __tests__/services/analytics/mixpanel.test.ts
  • components/providers/MixpanelSetup.tsx
  • services/analytics/mixpanel.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/services/analytics/mixpanel.test.ts

@GelatoGenesis GelatoGenesis merged commit ec48edd into main Mar 6, 2026
7 checks passed
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.

2 participants