Posthog behoben

This commit is contained in:
Timo Knuth 2025-12-11 11:40:38 +01:00
parent 8c5e2fa58e
commit 18f92c4285
8 changed files with 141 additions and 34 deletions

View File

@ -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 // Check for successful payment and verify session
useEffect(() => { useEffect(() => {
const success = searchParams.get('success'); const success = searchParams.get('success');

View File

@ -17,7 +17,16 @@ export default function AppLayout({
const { t } = useTranslation(); const { t } = useTranslation();
const [sidebarOpen, setSidebarOpen] = useState(false); 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 // Clear all cookies
document.cookie.split(";").forEach(c => { document.cookie.split(";").forEach(c => {
document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/");

View File

@ -36,6 +36,22 @@ export default function LoginPage() {
// Store user in localStorage for client-side // Store user in localStorage for client-side
localStorage.setItem('user', JSON.stringify(data.user)); 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 // Check for redirect parameter
const redirectUrl = searchParams.get('redirect') || '/dashboard'; const redirectUrl = searchParams.get('redirect') || '/dashboard';
router.push(redirectUrl); router.push(redirectUrl);

View File

@ -49,6 +49,23 @@ export default function SignupPage() {
// Store user in localStorage for client-side // Store user in localStorage for client-side
localStorage.setItem('user', JSON.stringify(data.user)); 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 // Redirect to dashboard
router.push('/dashboard'); router.push('/dashboard');
router.refresh(); router.refresh();

View File

@ -79,6 +79,8 @@ export async function GET(request: NextRequest) {
where: { email: userInfo.email }, where: { email: userInfo.email },
}); });
const isNewUser = !user;
// Create user if they don't exist // Create user if they don't exist
if (!user) { if (!user) {
user = await db.user.create({ user = await db.user.create({
@ -148,8 +150,12 @@ export async function GET(request: NextRequest) {
// Set authentication cookie // Set authentication cookie
cookies().set('userId', user.id, getAuthCookieOptions()); cookies().set('userId', user.id, getAuthCookieOptions());
// Redirect to dashboard // Redirect to dashboard with tracking params
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_APP_URL}/dashboard`); 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) { } catch (error) {
console.error('Google OAuth error:', error); console.error('Google OAuth error:', error);
return NextResponse.redirect( return NextResponse.redirect(

View File

@ -83,6 +83,7 @@ export async function POST(request: NextRequest) {
id: user.id, id: user.id,
name: user.name, name: user.name,
email: user.email, email: user.email,
plan: 'FREE',
}, },
}); });
} catch (error) { } catch (error) {

View File

@ -76,7 +76,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ return NextResponse.json({
success: true, 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) { } catch (error) {
console.error('Login error:', error); console.error('Login error:', error);

View File

@ -1,58 +1,74 @@
'use client'; 'use client';
import { useEffect } from 'react'; import { useEffect, useState, useRef } from 'react';
import { usePathname, useSearchParams } from 'next/navigation'; import { usePathname, useSearchParams } from 'next/navigation';
import posthog from 'posthog-js'; import posthog from 'posthog-js';
export function PostHogProvider({ children }: { children: React.ReactNode }) { export function PostHogProvider({ children }: { children: React.ReactNode }) {
const pathname = usePathname(); const pathname = usePathname();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [isInitialized, setIsInitialized] = useState(false);
const initializationAttempted = useRef(false);
// Initialize PostHog once
useEffect(() => { 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'); const cookieConsent = localStorage.getItem('cookieConsent');
// Only initialize PostHog if user has accepted cookies
if (cookieConsent === 'accepted') { if (cookieConsent === 'accepted') {
posthog.init('phc_97JBJVVQlqqiZuTVRHuBnnG9HasOv3GSsdeVjossizJ', { const apiKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
api_host: 'https://us.i.posthog.com', 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', person_profiles: 'identified_only',
capture_pageview: false, // We'll capture manually capture_pageview: false, // Manual pageview tracking
capture_pageleave: true, capture_pageleave: true,
autocapture: true, autocapture: true,
// Privacy-friendly settings
respect_dnt: true, respect_dnt: true,
opt_out_capturing_by_default: false, opt_out_capturing_by_default: false,
loaded: (posthog) => { });
// Enable debug mode in development
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
posthog.debug(); posthog.debug();
} }
},
}); // Set initialized immediately after init
setIsInitialized(true);
} else {
setIsInitialized(true); // Already loaded
}
} }
// Cleanup on unmount // NO cleanup function - PostHog should persist across page navigation
return () => {
if (cookieConsent === 'accepted') {
posthog.opt_out_capturing();
}
};
}, []); }, []);
// Track page views // Track page views ONLY after PostHog is initialized
useEffect(() => { useEffect(() => {
const cookieConsent = localStorage.getItem('cookieConsent'); const cookieConsent = localStorage.getItem('cookieConsent');
if (cookieConsent === 'accepted' && pathname) { if (cookieConsent === 'accepted' && pathname && isInitialized) {
let url = window.origin + pathname; let url = window.origin + pathname;
if (searchParams && searchParams.toString()) { if (searchParams && searchParams.toString()) {
url = url + `?${searchParams.toString()}`; url = url + `?${searchParams.toString()}`;
} }
posthog.capture('$pageview', { posthog.capture('$pageview', {
$current_url: url, $current_url: url,
}); });
} }
}, [pathname, searchParams]); }, [pathname, searchParams, isInitialized]); // Added isInitialized dependency
return <>{children}</>; return <>{children}</>;
} }
@ -62,7 +78,7 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
*/ */
export function identifyUser(userId: string, traits?: Record<string, any>) { export function identifyUser(userId: string, traits?: Record<string, any>) {
const cookieConsent = localStorage.getItem('cookieConsent'); const cookieConsent = localStorage.getItem('cookieConsent');
if (cookieConsent === 'accepted') { if (cookieConsent === 'accepted' && (posthog as any)._loaded) {
posthog.identify(userId, traits); posthog.identify(userId, traits);
} }
} }
@ -72,7 +88,7 @@ export function identifyUser(userId: string, traits?: Record<string, any>) {
*/ */
export function trackEvent(eventName: string, properties?: Record<string, any>) { export function trackEvent(eventName: string, properties?: Record<string, any>) {
const cookieConsent = localStorage.getItem('cookieConsent'); const cookieConsent = localStorage.getItem('cookieConsent');
if (cookieConsent === 'accepted') { if (cookieConsent === 'accepted' && (posthog as any)._loaded) {
posthog.capture(eventName, properties); posthog.capture(eventName, properties);
} }
} }
@ -82,7 +98,7 @@ export function trackEvent(eventName: string, properties?: Record<string, any>)
*/ */
export function resetUser() { export function resetUser() {
const cookieConsent = localStorage.getItem('cookieConsent'); const cookieConsent = localStorage.getItem('cookieConsent');
if (cookieConsent === 'accepted') { if (cookieConsent === 'accepted' && (posthog as any)._loaded) {
posthog.reset(); posthog.reset();
} }
} }