From d20aef295796f20e6aee3cd7145e04bdc9d0c4e8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Fri, 2 Jan 2026 01:10:16 -0800 Subject: [PATCH] feat: enhance login tracking and logout functionality - Added session storage flag to track local login success, ensuring OAuth flows do not double track login events. - Implemented tracking for logout events in both UserDropdown and AppSidebar components, resetting PostHog identity accordingly. - Minor formatting adjustments in GoogleLoginButton and footer-new components for consistency. --- .../app/(home)/login/GoogleLoginButton.tsx | 2 +- .../app/(home)/login/LocalLoginForm.tsx | 5 ++ surfsense_web/components/TokenHandler.tsx | 11 +++++ surfsense_web/components/UserDropdown.tsx | 5 ++ .../components/homepage/footer-new.tsx | 3 +- .../components/providers/PostHogIdentify.tsx | 49 +++++++++++++++++++ .../components/providers/PostHogProvider.tsx | 8 ++- .../components/sidebar/app-sidebar.tsx | 5 ++ 8 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 surfsense_web/components/providers/PostHogIdentify.tsx diff --git a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx index 1e7f7c4f8..e22fc2798 100644 --- a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx @@ -14,7 +14,7 @@ export function GoogleLoginButton() { trackLoginAttempt("google"); // IMPORTANT: Use the redirect-based authorize endpoint for cross-origin OAuth - // This fixes CSRF cookie issues in Firefox/Safari where cookies set via + // This fixes CSRF cookie issues in Firefox/Safari where cookies set via // cross-origin fetch requests may not be sent on subsequent redirects. // The authorize-redirect endpoint does a server-side redirect to Google // and sets the CSRF cookie properly for same-site context. diff --git a/surfsense_web/app/(home)/login/LocalLoginForm.tsx b/surfsense_web/app/(home)/login/LocalLoginForm.tsx index 5beadd63d..d632d09ed 100644 --- a/surfsense_web/app/(home)/login/LocalLoginForm.tsx +++ b/surfsense_web/app/(home)/login/LocalLoginForm.tsx @@ -54,6 +54,11 @@ export function LocalLoginForm() { // Track successful login trackLoginSuccess("local"); + // Set flag so TokenHandler knows local login was already tracked + if (typeof window !== "undefined") { + sessionStorage.setItem("login_success_tracked", "true"); + } + // Success toast toast.success(t("login_success"), { id: loadingToast, diff --git a/surfsense_web/components/TokenHandler.tsx b/surfsense_web/components/TokenHandler.tsx index 70119dfe4..42905ac0d 100644 --- a/surfsense_web/components/TokenHandler.tsx +++ b/surfsense_web/components/TokenHandler.tsx @@ -3,6 +3,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect } from "react"; import { getAndClearRedirectPath, setBearerToken } from "@/lib/auth-utils"; +import { trackLoginSuccess } from "@/lib/posthog/events"; interface TokenHandlerProps { redirectPath?: string; // Default path to redirect after storing token (if no saved path) @@ -36,6 +37,16 @@ const TokenHandler = ({ if (token) { try { + // Track login success for OAuth flows (e.g., Google) + // Local login already tracks success before redirecting here + const alreadyTracked = sessionStorage.getItem("login_success_tracked"); + if (!alreadyTracked) { + // This is an OAuth flow (Google login) - track success + trackLoginSuccess("google"); + } + // Clear the flag for future logins + sessionStorage.removeItem("login_success_tracked"); + // Store token in localStorage using both methods for compatibility localStorage.setItem(storageKey, token); setBearerToken(token); diff --git a/surfsense_web/components/UserDropdown.tsx b/surfsense_web/components/UserDropdown.tsx index ffdb9a31b..966193c7f 100644 --- a/surfsense_web/components/UserDropdown.tsx +++ b/surfsense_web/components/UserDropdown.tsx @@ -13,6 +13,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { resetUser, trackLogout } from "@/lib/posthog/events"; export function UserDropdown({ user, @@ -27,6 +28,10 @@ export function UserDropdown({ const handleLogout = () => { try { + // Track logout event and reset PostHog identity + trackLogout(); + resetUser(); + if (typeof window !== "undefined") { localStorage.removeItem("surfsense_bearer_token"); router.push("/"); diff --git a/surfsense_web/components/homepage/footer-new.tsx b/surfsense_web/components/homepage/footer-new.tsx index b6e6eab58..56b29b16b 100644 --- a/surfsense_web/components/homepage/footer-new.tsx +++ b/surfsense_web/components/homepage/footer-new.tsx @@ -96,9 +96,8 @@ export function FooterNew() {
- © SurfSense {new Date().getFullYear()}. All rights reserved. + © SurfSense {new Date().getFullYear()}. All rights reserved.
-
diff --git a/surfsense_web/components/providers/PostHogIdentify.tsx b/surfsense_web/components/providers/PostHogIdentify.tsx new file mode 100644 index 000000000..51fae8747 --- /dev/null +++ b/surfsense_web/components/providers/PostHogIdentify.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useAtomValue } from "jotai"; +import { useEffect, useRef } from "react"; +import { currentUserAtom } from "@/atoms/user/user-query.atoms"; +import { identifyUser, resetUser } from "@/lib/posthog/events"; + +/** + * Component that handles PostHog user identification. + * - Identifies users when they're logged in (user data is available) + * - Resets the PostHog identity when user logs out + * + * This should be rendered inside the PostHogProvider. + */ +export function PostHogIdentify() { + const { data: user, isSuccess, isError } = useAtomValue(currentUserAtom); + const previousUserIdRef = useRef(null); + + useEffect(() => { + // Only run on client side + if (typeof window === "undefined") return; + + // User is logged in and we have their data + if (isSuccess && user?.id) { + const userId = String(user.id); + + // Only identify if this is a new user or different from previous + if (previousUserIdRef.current !== userId) { + identifyUser(userId, { + email: user.email, + // Add any other user properties you want to track + is_superuser: user.is_superuser, + is_verified: user.is_verified, + }); + previousUserIdRef.current = userId; + } + } + + // User is not logged in (query failed due to auth error) + // and we previously had a user identified + if (isError && previousUserIdRef.current !== null) { + resetUser(); + previousUserIdRef.current = null; + } + }, [user, isSuccess, isError]); + + // This component doesn't render anything + return null; +} diff --git a/surfsense_web/components/providers/PostHogProvider.tsx b/surfsense_web/components/providers/PostHogProvider.tsx index 6f62afce7..2fcca1f9d 100644 --- a/surfsense_web/components/providers/PostHogProvider.tsx +++ b/surfsense_web/components/providers/PostHogProvider.tsx @@ -3,6 +3,7 @@ import { PostHogProvider as PHProvider } from "@posthog/react"; import posthog from "posthog-js"; import type { ReactNode } from "react"; +import { PostHogIdentify } from "./PostHogIdentify"; interface PostHogProviderProps { children: ReactNode; @@ -11,5 +12,10 @@ interface PostHogProviderProps { export function PostHogProvider({ children }: PostHogProviderProps) { // posthog-js is already initialized in instrumentation-client.ts // We just need to wrap the app with the PostHogProvider for hook access - return {children}; + return ( + + + {children} + + ); } diff --git a/surfsense_web/components/sidebar/app-sidebar.tsx b/surfsense_web/components/sidebar/app-sidebar.tsx index fb3bf3022..8030cb9d2 100644 --- a/surfsense_web/components/sidebar/app-sidebar.tsx +++ b/surfsense_web/components/sidebar/app-sidebar.tsx @@ -42,6 +42,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { resetUser, trackLogout } from "@/lib/posthog/events"; /** * Generates a consistent color based on a string (email) @@ -343,6 +344,10 @@ export const AppSidebar = memo(function AppSidebar({ const handleLogout = () => { try { + // Track logout event and reset PostHog identity + trackLogout(); + resetUser(); + if (typeof window !== "undefined") { localStorage.removeItem("surfsense_bearer_token"); router.push("/");