diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx
new file mode 100644
index 000000000..c45eca45c
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ApiKeyContent.tsx
@@ -0,0 +1,71 @@
+"use client";
+
+import { Check, Copy, Shield } from "lucide-react";
+import { AnimatePresence, motion } from "motion/react";
+import { useTranslations } from "next-intl";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { useApiKey } from "@/hooks/use-api-key";
+
+export function ApiKeyContent() {
+ const t = useTranslations("userSettings");
+ const { apiKey, isLoading, copied, copyToClipboard } = useApiKey();
+
+ return (
+
+
+
+
+ {t("api_key_warning_title")}
+ {t("api_key_warning_description")}
+
+
+
+
{t("your_api_key")}
+ {isLoading ? (
+
+ ) : apiKey ? (
+
+
+ {apiKey}
+
+
+
+
+
+
+ {copied ? t("copied") : t("copy")}
+
+
+
+ ) : (
+
{t("no_api_key")}
+ )}
+
+
+
+
{t("usage_title")}
+
{t("usage_description")}
+
+ Authorization: Bearer {apiKey || "YOUR_API_KEY"}
+
+
+
+
+ );
+}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx
new file mode 100644
index 000000000..a6cb11ecf
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx
@@ -0,0 +1,130 @@
+"use client";
+
+import { useAtomValue } from "jotai";
+import { AnimatePresence, motion } from "motion/react";
+import { useTranslations } from "next-intl";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+import { updateUserMutationAtom } from "@/atoms/user/user-mutation.atoms";
+import { currentUserAtom } from "@/atoms/user/user-query.atoms";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Spinner } from "@/components/ui/spinner";
+
+function AvatarDisplay({ url, fallback }: { url?: string; fallback: string }) {
+ const [hasError, setHasError] = useState(false);
+
+ useEffect(() => {
+ setHasError(false);
+ }, [url]);
+
+ if (url && !hasError) {
+ return (
+
setHasError(true)}
+ />
+ );
+ }
+
+ return (
+
+ {fallback}
+
+ );
+}
+
+export function ProfileContent() {
+ const t = useTranslations("userSettings");
+ const { data: user, isLoading: isUserLoading } = useAtomValue(currentUserAtom);
+ const { mutateAsync: updateUser, isPending } = useAtomValue(updateUserMutationAtom);
+
+ const [displayName, setDisplayName] = useState("");
+
+ useEffect(() => {
+ if (user) {
+ setDisplayName(user.display_name || "");
+ }
+ }, [user]);
+
+ const getInitials = (email: string) => {
+ const name = email.split("@")[0];
+ return name.slice(0, 2).toUpperCase();
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ try {
+ await updateUser({
+ display_name: displayName || null,
+ });
+ toast.success(t("profile_saved"));
+ } catch {
+ toast.error(t("profile_save_error"));
+ }
+ };
+
+ const hasChanges = displayName !== (user?.display_name || "");
+
+ return (
+
+
+ {isUserLoading ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx
new file mode 100644
index 000000000..019684a95
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import { UserKey, User } from "lucide-react";
+import { useTranslations } from "next-intl";
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/components/ui/animated-tabs";
+import { ApiKeyContent } from "./components/ApiKeyContent";
+import { ProfileContent } from "./components/ProfileContent";
+
+export default function UserSettingsPage() {
+ const t = useTranslations("userSettings");
+
+ return (
+
+
+
+
+
+
+ {t("profile_nav_label")}
+
+
+
+ {t("api_key_nav_label")}
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx b/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx
deleted file mode 100644
index 6bf10a78f..000000000
--- a/surfsense_web/app/dashboard/user/settings/components/ApiKeyContent.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-"use client";
-
-import { Check, Copy, Key, Menu, Shield } from "lucide-react";
-import { AnimatePresence, motion } from "motion/react";
-import { useTranslations } from "next-intl";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
-import { useApiKey } from "@/hooks/use-api-key";
-
-interface ApiKeyContentProps {
- onMenuClick: () => void;
-}
-
-export function ApiKeyContent({ onMenuClick }: ApiKeyContentProps) {
- const t = useTranslations("userSettings");
- const { apiKey, isLoading, copied, copyToClipboard } = useApiKey();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {t("api_key_title")}
-
-
{t("api_key_description")}
-
-
-
-
-
-
-
-
-
- {t("api_key_warning_title")}
- {t("api_key_warning_description")}
-
-
-
-
{t("your_api_key")}
- {isLoading ? (
-
- ) : apiKey ? (
-
-
- {apiKey}
-
-
-
-
-
-
- {copied ? t("copied") : t("copy")}
-
-
-
- ) : (
-
{t("no_api_key")}
- )}
-
-
-
-
{t("usage_title")}
-
{t("usage_description")}
-
- Authorization: Bearer {apiKey || "YOUR_API_KEY"}
-
-
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx b/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx
deleted file mode 100644
index a1ff4d781..000000000
--- a/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-"use client";
-
-import { useAtomValue } from "jotai";
-import { Menu, User } from "lucide-react";
-import { AnimatePresence, motion } from "motion/react";
-import { useTranslations } from "next-intl";
-import { useEffect, useState } from "react";
-import { toast } from "sonner";
-import { updateUserMutationAtom } from "@/atoms/user/user-mutation.atoms";
-import { currentUserAtom } from "@/atoms/user/user-query.atoms";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Spinner } from "@/components/ui/spinner";
-
-interface ProfileContentProps {
- onMenuClick: () => void;
-}
-
-function AvatarDisplay({ url, fallback }: { url?: string; fallback: string }) {
- const [hasError, setHasError] = useState(false);
-
- useEffect(() => {
- setHasError(false);
- }, [url]);
-
- if (url && !hasError) {
- return (
-
setHasError(true)}
- />
- );
- }
-
- return (
-
- {fallback}
-
- );
-}
-
-export function ProfileContent({ onMenuClick }: ProfileContentProps) {
- const t = useTranslations("userSettings");
- const { data: user, isLoading: isUserLoading } = useAtomValue(currentUserAtom);
- const { mutateAsync: updateUser, isPending } = useAtomValue(updateUserMutationAtom);
-
- const [displayName, setDisplayName] = useState("");
-
- useEffect(() => {
- if (user) {
- setDisplayName(user.display_name || "");
- }
- }, [user]);
-
- const getInitials = (email: string) => {
- const name = email.split("@")[0];
- return name.slice(0, 2).toUpperCase();
- };
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- try {
- await updateUser({
- display_name: displayName || null,
- });
- toast.success(t("profile_saved"));
- } catch {
- toast.error(t("profile_save_error"));
- }
- };
-
- const hasChanges = displayName !== (user?.display_name || "");
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {t("profile_title")}
-
-
{t("profile_description")}
-
-
-
-
-
-
-
- {isUserLoading ? (
-
-
-
- ) : (
-
- )}
-
-
-
-
-
- );
-}
diff --git a/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx b/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx
deleted file mode 100644
index 3424113a9..000000000
--- a/surfsense_web/app/dashboard/user/settings/components/UserSettingsSidebar.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-"use client";
-
-import type { LucideIcon } from "lucide-react";
-import { ArrowLeft, ChevronRight, X } from "lucide-react";
-import { AnimatePresence, motion } from "motion/react";
-import { useTranslations } from "next-intl";
-import { Button } from "@/components/ui/button";
-import { APP_VERSION } from "@/lib/env-config";
-import { cn } from "@/lib/utils";
-
-export interface SettingsNavItem {
- id: string;
- label: string;
- description: string;
- icon: LucideIcon;
-}
-
-interface UserSettingsSidebarProps {
- activeSection: string;
- onSectionChange: (section: string) => void;
- onBackToApp: () => void;
- isOpen: boolean;
- onClose: () => void;
- navItems: SettingsNavItem[];
-}
-
-export function UserSettingsSidebar({
- activeSection,
- onSectionChange,
- onBackToApp,
- isOpen,
- onClose,
- navItems,
-}: UserSettingsSidebarProps) {
- const t = useTranslations("userSettings");
-
- const handleNavClick = (sectionId: string) => {
- onSectionChange(sectionId);
- onClose();
- };
-
- return (
- <>
-
- {isOpen && (
-
- )}
-
-
-
- >
- );
-}
diff --git a/surfsense_web/app/dashboard/user/settings/page.tsx b/surfsense_web/app/dashboard/user/settings/page.tsx
deleted file mode 100644
index 8e04ce37a..000000000
--- a/surfsense_web/app/dashboard/user/settings/page.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-"use client";
-
-import { Key, User } from "lucide-react";
-import { motion } from "motion/react";
-import { useRouter } from "next/navigation";
-import { useTranslations } from "next-intl";
-import { useCallback, useState } from "react";
-import { ApiKeyContent } from "./components/ApiKeyContent";
-import { ProfileContent } from "./components/ProfileContent";
-import { type SettingsNavItem, UserSettingsSidebar } from "./components/UserSettingsSidebar";
-
-export default function UserSettingsPage() {
- const t = useTranslations("userSettings");
- const router = useRouter();
- const [activeSection, setActiveSection] = useState("profile");
- const [isSidebarOpen, setIsSidebarOpen] = useState(false);
-
- const navItems: SettingsNavItem[] = [
- {
- id: "profile",
- label: t("profile_nav_label"),
- description: t("profile_nav_description"),
- icon: User,
- },
- {
- id: "api-key",
- label: t("api_key_nav_label"),
- description: t("api_key_nav_description"),
- icon: Key,
- },
- ];
-
- const handleBackToApp = useCallback(() => {
- router.back();
- }, [router]);
-
- return (
-
-
-
-
setIsSidebarOpen(false)}
- navItems={navItems}
- />
- {activeSection === "profile" && (
- setIsSidebarOpen(true)} />
- )}
- {activeSection === "api-key" && (
- setIsSidebarOpen(true)} />
- )}
-
-
-
- );
-}
diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx
index c288aacb3..b0ff496f6 100644
--- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx
+++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx
@@ -304,8 +304,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
}, []);
const handleUserSettings = useCallback(() => {
- router.push("/dashboard/user/settings");
- }, [router]);
+ router.push(`/dashboard/${searchSpaceId}/user-settings`);
+ }, [router, searchSpaceId]);
const handleSearchSpaceSettings = useCallback(
(space: SearchSpace) => {
diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx
index 29925a555..4c3615086 100644
--- a/surfsense_web/components/sources/DocumentUploadTab.tsx
+++ b/surfsense_web/components/sources/DocumentUploadTab.tsx
@@ -303,7 +303,7 @@ export function DocumentUploadTab({
{!isFileCountLimitReached && (