diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 3ec435d..176cda9 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -132,6 +132,48 @@ export default function DashboardPage() { }, ]; + // Track Google OAuth login/signup + useEffect(() => { + const authMethod = searchParams.get('authMethod'); + const isNewUser = searchParams.get('isNewUser') === 'true'; + + if (authMethod === 'google') { + const trackGoogleAuth = async () => { + try { + // Fetch user data from API (cookie-based auth) + const response = await fetch('/api/user'); + if (!response.ok) return; + + const user = await response.json(); + + // Store in localStorage for consistency + localStorage.setItem('user', JSON.stringify(user)); + + const { identifyUser, trackEvent } = await import('@/components/PostHogProvider'); + identifyUser(user.id, { + email: user.email, + name: user.name, + plan: user.plan || 'FREE', + provider: 'google', + }); + + trackEvent(isNewUser ? 'user_signup' : 'user_login', { + method: 'google', + email: user.email, + isNewUser, + }); + + // Clean up URL params + router.replace('/dashboard'); + } catch (error) { + console.error('PostHog tracking error:', error); + } + }; + + trackGoogleAuth(); + } + }, [searchParams, router]); + // Check for successful payment and verify session useEffect(() => { const success = searchParams.get('success'); diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 2d8672d..5c65d08 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -17,7 +17,16 @@ export default function AppLayout({ const { t } = useTranslation(); const [sidebarOpen, setSidebarOpen] = useState(false); - const handleSignOut = () => { + const handleSignOut = async () => { + // Track logout event before clearing data + try { + const { trackEvent, resetUser } = await import('@/components/PostHogProvider'); + trackEvent('user_logout'); + resetUser(); // Reset PostHog user session + } catch (error) { + console.error('PostHog tracking error:', error); + } + // Clear all cookies document.cookie.split(";").forEach(c => { document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index a22f552..440505a 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -36,6 +36,22 @@ export default function LoginPage() { // Store user in localStorage for client-side localStorage.setItem('user', JSON.stringify(data.user)); + // Track successful login with PostHog + try { + const { identifyUser, trackEvent } = await import('@/components/PostHogProvider'); + identifyUser(data.user.id, { + email: data.user.email, + name: data.user.name, + plan: data.user.plan || 'FREE', + }); + trackEvent('user_login', { + method: 'email', + email: data.user.email, + }); + } catch (error) { + console.error('PostHog tracking error:', error); + } + // Check for redirect parameter const redirectUrl = searchParams.get('redirect') || '/dashboard'; router.push(redirectUrl); diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index adbaedd..4782134 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -49,6 +49,23 @@ export default function SignupPage() { // Store user in localStorage for client-side localStorage.setItem('user', JSON.stringify(data.user)); + // Track successful signup with PostHog + try { + const { identifyUser, trackEvent } = await import('@/components/PostHogProvider'); + identifyUser(data.user.id, { + email: data.user.email, + name: data.user.name, + plan: data.user.plan || 'FREE', + signupMethod: 'email', + }); + trackEvent('user_signup', { + method: 'email', + email: data.user.email, + }); + } catch (error) { + console.error('PostHog tracking error:', error); + } + // Redirect to dashboard router.push('/dashboard'); router.refresh(); diff --git a/src/app/api/auth/google/route.ts b/src/app/api/auth/google/route.ts index 54d6053..79c053a 100644 --- a/src/app/api/auth/google/route.ts +++ b/src/app/api/auth/google/route.ts @@ -79,6 +79,8 @@ export async function GET(request: NextRequest) { where: { email: userInfo.email }, }); + const isNewUser = !user; + // Create user if they don't exist if (!user) { user = await db.user.create({ @@ -148,8 +150,12 @@ export async function GET(request: NextRequest) { // Set authentication cookie cookies().set('userId', user.id, getAuthCookieOptions()); - // Redirect to dashboard - return NextResponse.redirect(`${process.env.NEXT_PUBLIC_APP_URL}/dashboard`); + // Redirect to dashboard with tracking params + const redirectUrl = new URL(`${process.env.NEXT_PUBLIC_APP_URL}/dashboard`); + redirectUrl.searchParams.set('authMethod', 'google'); + redirectUrl.searchParams.set('isNewUser', isNewUser.toString()); + + return NextResponse.redirect(redirectUrl.toString()); } catch (error) { console.error('Google OAuth error:', error); return NextResponse.redirect( diff --git a/src/app/api/auth/signup/route.ts b/src/app/api/auth/signup/route.ts index 4eff156..49c3740 100644 --- a/src/app/api/auth/signup/route.ts +++ b/src/app/api/auth/signup/route.ts @@ -83,6 +83,7 @@ export async function POST(request: NextRequest) { id: user.id, name: user.name, email: user.email, + plan: 'FREE', }, }); } catch (error) { diff --git a/src/app/api/auth/simple-login/route.ts b/src/app/api/auth/simple-login/route.ts index b74cd87..009ad82 100644 --- a/src/app/api/auth/simple-login/route.ts +++ b/src/app/api/auth/simple-login/route.ts @@ -76,7 +76,7 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: true, - user: { id: user.id, email: user.email, name: user.name } + user: { id: user.id, email: user.email, name: user.name, plan: user.plan || 'FREE' } }); } catch (error) { console.error('Login error:', error); diff --git a/src/components/PostHogProvider.tsx b/src/components/PostHogProvider.tsx index d6ec688..560071b 100644 --- a/src/components/PostHogProvider.tsx +++ b/src/components/PostHogProvider.tsx @@ -1,58 +1,74 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { usePathname, useSearchParams } from 'next/navigation'; import posthog from 'posthog-js'; export function PostHogProvider({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const searchParams = useSearchParams(); + const [isInitialized, setIsInitialized] = useState(false); + const initializationAttempted = useRef(false); + // Initialize PostHog once useEffect(() => { - // Check if user has consented to analytics cookies + // Prevent double initialization in React Strict Mode + if (initializationAttempted.current) return; + initializationAttempted.current = true; + const cookieConsent = localStorage.getItem('cookieConsent'); - // Only initialize PostHog if user has accepted cookies if (cookieConsent === 'accepted') { - posthog.init('phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ', { - api_host: 'https://us.i.posthog.com', - person_profiles: 'identified_only', - capture_pageview: false, // We'll capture manually - capture_pageleave: true, - autocapture: true, - // Privacy-friendly settings - respect_dnt: true, - opt_out_capturing_by_default: false, - loaded: (posthog) => { - if (process.env.NODE_ENV === 'development') { - posthog.debug(); - } - }, - }); + const apiKey = process.env.NEXT_PUBLIC_POSTHOG_KEY; + const apiHost = process.env.NEXT_PUBLIC_POSTHOG_HOST; + + if (!apiKey) { + console.warn('PostHog API key not configured'); + return; + } + + // Check if already initialized (using _loaded property) + if (!(posthog as any)._loaded) { + posthog.init(apiKey, { + api_host: apiHost || 'https://us.i.posthog.com', + person_profiles: 'identified_only', + capture_pageview: false, // Manual pageview tracking + capture_pageleave: true, + autocapture: true, + respect_dnt: true, + opt_out_capturing_by_default: false, + }); + + // Enable debug mode in development + if (process.env.NODE_ENV === 'development') { + posthog.debug(); + } + + // Set initialized immediately after init + setIsInitialized(true); + } else { + setIsInitialized(true); // Already loaded + } } - // Cleanup on unmount - return () => { - if (cookieConsent === 'accepted') { - posthog.opt_out_capturing(); - } - }; + // NO cleanup function - PostHog should persist across page navigation }, []); - // Track page views + // Track page views ONLY after PostHog is initialized useEffect(() => { const cookieConsent = localStorage.getItem('cookieConsent'); - if (cookieConsent === 'accepted' && pathname) { + if (cookieConsent === 'accepted' && pathname && isInitialized) { let url = window.origin + pathname; if (searchParams && searchParams.toString()) { url = url + `?${searchParams.toString()}`; } + posthog.capture('$pageview', { $current_url: url, }); } - }, [pathname, searchParams]); + }, [pathname, searchParams, isInitialized]); // Added isInitialized dependency return <>{children}; } @@ -62,7 +78,7 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) { */ export function identifyUser(userId: string, traits?: Record) { const cookieConsent = localStorage.getItem('cookieConsent'); - if (cookieConsent === 'accepted') { + if (cookieConsent === 'accepted' && (posthog as any)._loaded) { posthog.identify(userId, traits); } } @@ -72,7 +88,7 @@ export function identifyUser(userId: string, traits?: Record) { */ export function trackEvent(eventName: string, properties?: Record) { const cookieConsent = localStorage.getItem('cookieConsent'); - if (cookieConsent === 'accepted') { + if (cookieConsent === 'accepted' && (posthog as any)._loaded) { posthog.capture(eventName, properties); } } @@ -82,7 +98,7 @@ export function trackEvent(eventName: string, properties?: Record) */ export function resetUser() { const cookieConsent = localStorage.getItem('cookieConsent'); - if (cookieConsent === 'accepted') { + if (cookieConsent === 'accepted' && (posthog as any)._loaded) { posthog.reset(); } }