mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 17:26:23 +02:00
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.
This commit is contained in:
parent
458c152032
commit
d20aef2957
8 changed files with 84 additions and 4 deletions
|
|
@ -14,7 +14,7 @@ export function GoogleLoginButton() {
|
||||||
trackLoginAttempt("google");
|
trackLoginAttempt("google");
|
||||||
|
|
||||||
// IMPORTANT: Use the redirect-based authorize endpoint for cross-origin OAuth
|
// 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.
|
// cross-origin fetch requests may not be sent on subsequent redirects.
|
||||||
// The authorize-redirect endpoint does a server-side redirect to Google
|
// The authorize-redirect endpoint does a server-side redirect to Google
|
||||||
// and sets the CSRF cookie properly for same-site context.
|
// and sets the CSRF cookie properly for same-site context.
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,11 @@ export function LocalLoginForm() {
|
||||||
// Track successful login
|
// Track successful login
|
||||||
trackLoginSuccess("local");
|
trackLoginSuccess("local");
|
||||||
|
|
||||||
|
// Set flag so TokenHandler knows local login was already tracked
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
sessionStorage.setItem("login_success_tracked", "true");
|
||||||
|
}
|
||||||
|
|
||||||
// Success toast
|
// Success toast
|
||||||
toast.success(t("login_success"), {
|
toast.success(t("login_success"), {
|
||||||
id: loadingToast,
|
id: loadingToast,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { getAndClearRedirectPath, setBearerToken } from "@/lib/auth-utils";
|
import { getAndClearRedirectPath, setBearerToken } from "@/lib/auth-utils";
|
||||||
|
import { trackLoginSuccess } from "@/lib/posthog/events";
|
||||||
|
|
||||||
interface TokenHandlerProps {
|
interface TokenHandlerProps {
|
||||||
redirectPath?: string; // Default path to redirect after storing token (if no saved path)
|
redirectPath?: string; // Default path to redirect after storing token (if no saved path)
|
||||||
|
|
@ -36,6 +37,16 @@ const TokenHandler = ({
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
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
|
// Store token in localStorage using both methods for compatibility
|
||||||
localStorage.setItem(storageKey, token);
|
localStorage.setItem(storageKey, token);
|
||||||
setBearerToken(token);
|
setBearerToken(token);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { resetUser, trackLogout } from "@/lib/posthog/events";
|
||||||
|
|
||||||
export function UserDropdown({
|
export function UserDropdown({
|
||||||
user,
|
user,
|
||||||
|
|
@ -27,6 +28,10 @@ export function UserDropdown({
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
try {
|
try {
|
||||||
|
// Track logout event and reset PostHog identity
|
||||||
|
trackLogout();
|
||||||
|
resetUser();
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
localStorage.removeItem("surfsense_bearer_token");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,8 @@ export function FooterNew() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 ml-2">
|
<div className="mt-2 ml-2">
|
||||||
© SurfSense {new Date().getFullYear()}. All rights reserved.
|
© SurfSense {new Date().getFullYear()}. All rights reserved.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-10 items-start mt-10 sm:mt-0 md:mt-0">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-10 items-start mt-10 sm:mt-0 md:mt-0">
|
||||||
<div className="flex justify-center space-y-4 flex-col w-full">
|
<div className="flex justify-center space-y-4 flex-col w-full">
|
||||||
|
|
|
||||||
49
surfsense_web/components/providers/PostHogIdentify.tsx
Normal file
49
surfsense_web/components/providers/PostHogIdentify.tsx
Normal file
|
|
@ -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<string | null>(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;
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { PostHogProvider as PHProvider } from "@posthog/react";
|
import { PostHogProvider as PHProvider } from "@posthog/react";
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { PostHogIdentify } from "./PostHogIdentify";
|
||||||
|
|
||||||
interface PostHogProviderProps {
|
interface PostHogProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|
@ -11,5 +12,10 @@ interface PostHogProviderProps {
|
||||||
export function PostHogProvider({ children }: PostHogProviderProps) {
|
export function PostHogProvider({ children }: PostHogProviderProps) {
|
||||||
// posthog-js is already initialized in instrumentation-client.ts
|
// posthog-js is already initialized in instrumentation-client.ts
|
||||||
// We just need to wrap the app with the PostHogProvider for hook access
|
// We just need to wrap the app with the PostHogProvider for hook access
|
||||||
return <PHProvider client={posthog}>{children}</PHProvider>;
|
return (
|
||||||
|
<PHProvider client={posthog}>
|
||||||
|
<PostHogIdentify />
|
||||||
|
{children}
|
||||||
|
</PHProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { resetUser, trackLogout } from "@/lib/posthog/events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a consistent color based on a string (email)
|
* Generates a consistent color based on a string (email)
|
||||||
|
|
@ -343,6 +344,10 @@ export const AppSidebar = memo(function AppSidebar({
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
try {
|
try {
|
||||||
|
// Track logout event and reset PostHog identity
|
||||||
|
trackLogout();
|
||||||
|
resetUser();
|
||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
localStorage.removeItem("surfsense_bearer_token");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue