Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions surfsense_web/app/(home)/login/GoogleLoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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`, {
Expand All @@ -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);
});
};
Expand Down
11 changes: 11 additions & 0 deletions surfsense_web/app/(home)/login/LocalLoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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"));

Expand All @@ -47,6 +51,9 @@ export function LocalLoginForm() {
grant_type: "password",
});

// Track successful login
trackLoginSuccess("local");

// Success toast
toast.success(t("login_success"), {
id: loadingToast,
Expand All @@ -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,
Expand All @@ -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);

Expand Down
16 changes: 16 additions & 0 deletions surfsense_web/app/(home)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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"));

Expand All @@ -64,6 +72,9 @@ export default function RegisterPage() {
is_verified: false,
});

// Track successful registration
trackRegistrationSuccess();

// Success toast
toast.success(t("register_success"), {
id: loadingToast,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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) => ({
Expand Down Expand Up @@ -653,13 +670,24 @@ 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") {
// Request was cancelled
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) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 (
<div className="container mx-auto py-8 px-4 min-h-[calc(100vh-64px)]">
<motion.div
Expand Down
4 changes: 4 additions & 0 deletions surfsense_web/app/dashboard/searchspaces/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { createSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { SearchSpaceForm } from "@/components/search-space-form";
import { trackSearchSpaceCreated } from "@/lib/posthog/events";

export default function SearchSpacesPage() {
const router = useRouter();
Expand All @@ -16,6 +17,9 @@ export default function SearchSpacesPage() {
description: data.description || "",
});

// Track search space creation
trackSearchSpaceCreated(result.id, data.name);

// Redirect to the newly created search space's onboarding
router.push(`/dashboard/${result.id}/onboard`);

Expand Down
33 changes: 18 additions & 15 deletions surfsense_web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GoogleAnalytics } from "@next/third-parties/google";
import { RootProvider } from "fumadocs-ui/provider/next";
import { Roboto } from "next/font/google";
import { I18nProvider } from "@/components/providers/I18nProvider";
import { PostHogProvider } from "@/components/providers/PostHogProvider";
import { ThemeProvider } from "@/components/theme/theme-provider";
import { Toaster } from "@/components/ui/sonner";
import { LocaleProvider } from "@/contexts/LocaleContext";
Expand Down Expand Up @@ -93,21 +94,23 @@ export default function RootLayout({
<html lang="en" suppressHydrationWarning>
<GoogleAnalytics gaId="G-T4CHE7W3TE" />
<body className={cn(roboto.className, "bg-white dark:bg-black antialiased h-full w-full ")}>
<LocaleProvider>
<I18nProvider>
<ThemeProvider
attribute="class"
enableSystem
disableTransitionOnChange
defaultTheme="light"
>
<RootProvider>
<ReactQueryClientProvider>{children}</ReactQueryClientProvider>
<Toaster />
</RootProvider>
</ThemeProvider>
</I18nProvider>
</LocaleProvider>
<PostHogProvider>
<LocaleProvider>
<I18nProvider>
<ThemeProvider
attribute="class"
enableSystem
disableTransitionOnChange
defaultTheme="light"
>
<RootProvider>
<ReactQueryClientProvider>{children}</ReactQueryClientProvider>
<Toaster />
</RootProvider>
</ThemeProvider>
</I18nProvider>
</LocaleProvider>
</PostHogProvider>
</body>
</html>
);
Expand Down
15 changes: 15 additions & 0 deletions surfsense_web/components/providers/PostHogProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { PostHogProvider as PHProvider } from "@posthog/react";
import posthog from "posthog-js";
import type { ReactNode } from "react";

interface PostHogProviderProps {
children: ReactNode;
}

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 <PHProvider client={posthog}>{children}</PHProvider>;
}
18 changes: 17 additions & 1 deletion surfsense_web/components/sources/DocumentUploadTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
import { toast } from "sonner";
import { uploadDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";

import { Alert, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator";
import {
trackDocumentUploadFailure,
trackDocumentUploadStarted,
trackDocumentUploadSuccess,
} from "@/lib/posthog/events";
import { GridPattern } from "./GridPattern";

interface DocumentUploadTabProps {
Expand Down Expand Up @@ -154,6 +158,10 @@ export function DocumentUploadTab({ searchSpaceId }: DocumentUploadTabProps) {
const handleUpload = async () => {
setUploadProgress(0);

// Track upload started
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
trackDocumentUploadStarted(Number(searchSpaceId), files.length, totalSize);

// Create a progress interval to simulate progress
const progressInterval = setInterval(() => {
setUploadProgress((prev) => {
Expand All @@ -172,6 +180,10 @@ export function DocumentUploadTab({ searchSpaceId }: DocumentUploadTabProps) {
onSuccess: () => {
clearInterval(progressInterval);
setUploadProgress(100);

// Track upload success
trackDocumentUploadSuccess(Number(searchSpaceId), files.length);

toast(t("upload_initiated"), {
description: t("upload_initiated_desc"),
});
Expand All @@ -180,6 +192,10 @@ export function DocumentUploadTab({ searchSpaceId }: DocumentUploadTabProps) {
onError: (error: any) => {
clearInterval(progressInterval);
setUploadProgress(0);

// Track upload failure
trackDocumentUploadFailure(Number(searchSpaceId), error.message || "Upload failed");

toast(t("upload_error"), {
description: `${t("upload_error_desc")}: ${error.message || "Upload failed"}`,
});
Expand Down
13 changes: 13 additions & 0 deletions surfsense_web/instrumentation-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import posthog from "posthog-js";

if (process.env.NEXT_PUBLIC_POSTHOG_KEY) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
defaults: "2025-11-30",
// Disable automatic pageview capture, as we capture manually with PostHogProvider
// This ensures proper pageview tracking with Next.js client-side navigation
capture_pageview: "history_change",
// Enable session recording
capture_pageleave: true,
});
}
Loading
Loading