diff --git a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx index 6eef58aed..d4d9d4b4a 100644 --- a/surfsense_web/app/(home)/login/GoogleLoginButton.tsx +++ b/surfsense_web/app/(home)/login/GoogleLoginButton.tsx @@ -3,12 +3,16 @@ import { IconBrandGoogleFilled } from "@tabler/icons-react"; import { motion } from "motion/react"; import { useTranslations } from "next-intl"; import { Logo } from "@/components/Logo"; +import { trackLoginAttempt, trackLoginFailure } from "@/lib/posthog/events"; import { AmbientBackground } from "./AmbientBackground"; export function GoogleLoginButton() { const t = useTranslations("auth"); const handleGoogleLogin = () => { + // Track Google login attempt + trackLoginAttempt("google"); + // Redirect to Google OAuth authorization URL // credentials: 'include' is required to accept the CSRF cookie from cross-origin response fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/google/authorize`, { @@ -24,10 +28,12 @@ export function GoogleLoginButton() { if (data.authorization_url) { window.location.href = data.authorization_url; } else { + trackLoginFailure("google", "No authorization URL received"); console.error("No authorization URL received"); } }) .catch((error) => { + trackLoginFailure("google", error?.message || "Unknown error"); console.error("Error during Google login:", error); }); }; diff --git a/surfsense_web/app/(home)/login/LocalLoginForm.tsx b/surfsense_web/app/(home)/login/LocalLoginForm.tsx index 0157c9faf..44e9b27c2 100644 --- a/surfsense_web/app/(home)/login/LocalLoginForm.tsx +++ b/surfsense_web/app/(home)/login/LocalLoginForm.tsx @@ -10,6 +10,7 @@ import { toast } from "sonner"; import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms"; import { getAuthErrorDetails, isNetworkError, shouldRetry } from "@/lib/auth-errors"; import { ValidationError } from "@/lib/error"; +import { trackLoginAttempt, trackLoginFailure, trackLoginSuccess } from "@/lib/posthog/events"; export function LocalLoginForm() { const t = useTranslations("auth"); @@ -37,6 +38,9 @@ export function LocalLoginForm() { e.preventDefault(); setError({ title: null, message: null }); // Clear any previous errors + // Track login attempt + trackLoginAttempt("local"); + // Show loading toast const loadingToast = toast.loading(tCommon("loading")); @@ -47,6 +51,9 @@ export function LocalLoginForm() { grant_type: "password", }); + // Track successful login + trackLoginSuccess("local"); + // Success toast toast.success(t("login_success"), { id: loadingToast, @@ -60,6 +67,7 @@ export function LocalLoginForm() { }, 500); } catch (err) { if (err instanceof ValidationError) { + trackLoginFailure("local", err.message); setError({ title: err.name, message: err.message }); toast.error(err.name, { id: loadingToast, @@ -78,6 +86,9 @@ export function LocalLoginForm() { errorCode = "NETWORK_ERROR"; } + // Track login failure + trackLoginFailure("local", errorCode); + // Get detailed error information from auth-errors utility const errorDetails = getAuthErrorDetails(errorCode); diff --git a/surfsense_web/app/(home)/register/page.tsx b/surfsense_web/app/(home)/register/page.tsx index c535832be..4a8dce546 100644 --- a/surfsense_web/app/(home)/register/page.tsx +++ b/surfsense_web/app/(home)/register/page.tsx @@ -11,6 +11,11 @@ import { registerMutationAtom } from "@/atoms/auth/auth-mutation.atoms"; import { Logo } from "@/components/Logo"; import { getAuthErrorDetails, isNetworkError, shouldRetry } from "@/lib/auth-errors"; import { AppError, ValidationError } from "@/lib/error"; +import { + trackRegistrationAttempt, + trackRegistrationFailure, + trackRegistrationSuccess, +} from "@/lib/posthog/events"; import { AmbientBackground } from "../login/AmbientBackground"; export default function RegisterPage() { @@ -52,6 +57,9 @@ export default function RegisterPage() { setError({ title: null, message: null }); // Clear any previous errors + // Track registration attempt + trackRegistrationAttempt(); + // Show loading toast const loadingToast = toast.loading(t("creating_account")); @@ -64,6 +72,9 @@ export default function RegisterPage() { is_verified: false, }); + // Track successful registration + trackRegistrationSuccess(); + // Success toast toast.success(t("register_success"), { id: loadingToast, @@ -81,6 +92,7 @@ export default function RegisterPage() { case 403: { const friendlyMessage = "Registrations are currently closed. If you need access, contact your administrator."; + trackRegistrationFailure("Registration disabled"); setError({ title: "Registration is disabled", message: friendlyMessage }); toast.error("Registration is disabled", { id: loadingToast, @@ -94,6 +106,7 @@ export default function RegisterPage() { } if (err instanceof ValidationError) { + trackRegistrationFailure(err.message); setError({ title: err.name, message: err.message }); toast.error(err.name, { id: loadingToast, @@ -113,6 +126,9 @@ export default function RegisterPage() { errorCode = "NETWORK_ERROR"; } + // Track registration failure + trackRegistrationFailure(errorCode); + // Get detailed error information from auth-errors utility const errorDetails = getAuthErrorDetails(errorCode); diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index f288bfa59..dbea83fb7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -37,6 +37,12 @@ import { getThreadMessages, type MessageRecord, } from "@/lib/chat/thread-persistence"; +import { + trackChatCreated, + trackChatError, + trackChatMessageSent, + trackChatResponseReceived, +} from "@/lib/posthog/events"; /** * Extract thinking steps from message content @@ -305,6 +311,10 @@ export default function NewChatPage() { const newThread = await createThread(searchSpaceId, "New Chat"); currentThreadId = newThread.id; setThreadId(currentThreadId); + + // Track chat creation + trackChatCreated(searchSpaceId, currentThreadId); + // Update URL silently using browser API (not router.replace) to avoid // interrupting the ongoing fetch/streaming with React navigation window.history.replaceState( @@ -331,6 +341,13 @@ export default function NewChatPage() { }; setMessages((prev) => [...prev, userMessage]); + // Track message sent + trackChatMessageSent(searchSpaceId, currentThreadId, { + hasAttachments: messageAttachments.length > 0, + hasMentionedDocuments: mentionedDocumentIds.length > 0, + messageLength: userQuery.length, + }); + // Store mentioned documents with this message for display if (mentionedDocuments.length > 0) { const docsInfo: MentionedDocumentInfo[] = mentionedDocuments.map((doc) => ({ @@ -653,6 +670,9 @@ export default function NewChatPage() { role: "assistant", content: finalContent, }).catch((err) => console.error("Failed to persist assistant message:", err)); + + // Track successful response + trackChatResponseReceived(searchSpaceId, currentThreadId); } } catch (error) { if (error instanceof Error && error.name === "AbortError") { @@ -660,6 +680,14 @@ export default function NewChatPage() { return; } console.error("[NewChatPage] Chat error:", error); + + // Track chat error + trackChatError( + searchSpaceId, + currentThreadId, + error instanceof Error ? error.message : "Unknown error" + ); + toast.error("Failed to get response. Please try again."); // Update assistant message with error setMessages((prev) => diff --git a/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx index ad96402a4..48efcd922 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx @@ -13,11 +13,12 @@ import { } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useParams, useRouter } from "next/navigation"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { LLMRoleManager } from "@/components/settings/llm-role-manager"; import { ModelConfigManager } from "@/components/settings/model-config-manager"; import { PromptConfigManager } from "@/components/settings/prompt-config-manager"; import { Button } from "@/components/ui/button"; +import { trackSettingsViewed } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; interface SettingsNavItem { @@ -271,6 +272,11 @@ export default function SettingsPage() { const [activeSection, setActiveSection] = useState("models"); const [isSidebarOpen, setIsSidebarOpen] = useState(false); + // Track settings section view + useEffect(() => { + trackSettingsViewed(searchSpaceId, activeSection); + }, [searchSpaceId, activeSection]); + const handleBackToApp = useCallback(() => { router.push(`/dashboard/${searchSpaceId}/new-chat`); }, [router, searchSpaceId]); diff --git a/surfsense_web/app/dashboard/[search_space_id]/sources/add/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/sources/add/page.tsx index 3c9b57f98..e0729e29b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/sources/add/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/sources/add/page.tsx @@ -9,6 +9,7 @@ import { ConnectorsTab } from "@/components/sources/ConnectorsTab"; import { DocumentUploadTab } from "@/components/sources/DocumentUploadTab"; import { YouTubeTab } from "@/components/sources/YouTubeTab"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { trackSourcesTabViewed } from "@/lib/posthog/events"; export default function AddSourcesPage() { const params = useParams(); @@ -30,9 +31,16 @@ export default function AddSourcesPage() { router.push(`/dashboard/${search_space_id}/connectors/add/webcrawler-connector`); } else { setActiveTab(value); + // Track tab view + trackSourcesTabViewed(Number(search_space_id), value); } }; + // Track initial tab view + useEffect(() => { + trackSourcesTabViewed(Number(search_space_id), activeTab); + }, []); + return (