diff --git a/surfsense_web/app/dashboard/user/settings/page.tsx b/surfsense_web/app/dashboard/user/settings/page.tsx new file mode 100644 index 000000000..508ff65b5 --- /dev/null +++ b/surfsense_web/app/dashboard/user/settings/page.tsx @@ -0,0 +1,323 @@ +"use client"; + +import { + ArrowLeft, + Check, + ChevronRight, + Copy, + Key, + type LucideIcon, + Menu, + Shield, + X, +} from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; +import { useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { useCallback, useState } from "react"; +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"; +import { cn } from "@/lib/utils"; + +interface SettingsNavItem { + id: string; + label: string; + description: string; + icon: LucideIcon; +} + +function UserSettingsSidebar({ + activeSection, + onSectionChange, + onBackToApp, + isOpen, + onClose, + navItems, +}: { + activeSection: string; + onSectionChange: (section: string) => void; + onBackToApp: () => void; + isOpen: boolean; + onClose: () => void; + navItems: SettingsNavItem[]; +}) { + const t = useTranslations("userSettings"); + + const handleNavClick = (sectionId: string) => { + onSectionChange(sectionId); + onClose(); + }; + + return ( + <> + + {isOpen && ( + + )} + + + + + ); +} + +function ApiKeyContent({ onMenuClick }: { onMenuClick: () => void }) { + 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"}
+								
+
+ + +
+
+ + ); +} + +export default function UserSettingsPage() { + const t = useTranslations("userSettings"); + const router = useRouter(); + const [activeSection, setActiveSection] = useState("api-key"); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + const navItems: SettingsNavItem[] = [ + { + 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 === "api-key" && ( + setIsSidebarOpen(true)} /> + )} + + ); +} diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index cdf354c48..8f42e22aa 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -214,6 +214,10 @@ export function LayoutDataProvider({ setIsAllSearchSpacesSheetOpen(true); }, []); + const handleUserSettings = useCallback(() => { + router.push("/dashboard/user/settings"); + }, [router]); + const handleSearchSpaceSettings = useCallback( (id: number) => { router.push(`/dashboard/${id}/settings`); @@ -396,6 +400,7 @@ export function LayoutDataProvider({ onSettings={handleSettings} onManageMembers={handleManageMembers} onSeeAllSearchSpaces={handleSeeAllSearchSpaces} + onUserSettings={handleUserSettings} onLogout={handleLogout} pageUsage={pageUsage} breadcrumb={breadcrumb} diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index 50f963fb9..ee2978113 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -41,6 +41,7 @@ interface LayoutShellProps { onSettings?: () => void; onManageMembers?: () => void; onSeeAllSearchSpaces?: () => void; + onUserSettings?: () => void; onLogout?: () => void; pageUsage?: PageUsage; breadcrumb?: React.ReactNode; @@ -77,6 +78,7 @@ export function LayoutShell({ onSettings, onManageMembers, onSeeAllSearchSpaces, + onUserSettings, onLogout, pageUsage, breadcrumb, @@ -131,6 +133,7 @@ export function LayoutShell({ onSettings={onSettings} onManageMembers={onManageMembers} onSeeAllSearchSpaces={onSeeAllSearchSpaces} + onUserSettings={onUserSettings} onLogout={onLogout} pageUsage={pageUsage} /> @@ -179,6 +182,7 @@ export function LayoutShell({ onSettings={onSettings} onManageMembers={onManageMembers} onSeeAllSearchSpaces={onSeeAllSearchSpaces} + onUserSettings={onUserSettings} onLogout={onLogout} pageUsage={pageUsage} className="hidden md:flex border-r shrink-0" diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 240121e0d..69dcb7391 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -45,6 +45,7 @@ interface SidebarProps { onSettings?: () => void; onManageMembers?: () => void; onSeeAllSearchSpaces?: () => void; + onUserSettings?: () => void; onLogout?: () => void; pageUsage?: PageUsage; className?: string; @@ -72,6 +73,7 @@ export function Sidebar({ onSettings, onManageMembers, onSeeAllSearchSpaces, + onUserSettings, onLogout, pageUsage, className, @@ -287,7 +289,7 @@ export function Sidebar({ )} - +
); diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx index 29b35b9a9..d3e97c8eb 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx @@ -1,6 +1,6 @@ "use client"; -import { ChevronUp, LogOut } from "lucide-react"; +import { ChevronUp, LogOut, Settings } from "lucide-react"; import { useTranslations } from "next-intl"; import { DropdownMenu, @@ -16,6 +16,7 @@ import type { User } from "../../types/layout.types"; interface SidebarUserProfileProps { user: User; + onUserSettings?: () => void; onLogout?: () => void; isCollapsed?: boolean; } @@ -62,6 +63,7 @@ function getInitials(email: string): string { export function SidebarUserProfile({ user, + onUserSettings, onLogout, isCollapsed = false, }: SidebarUserProfileProps) { @@ -117,6 +119,13 @@ export function SidebarUserProfile({ + + + {t("user_settings")} + + + + {t("logout")} @@ -177,6 +186,13 @@ export function SidebarUserProfile({ + + + {t("user_settings")} + + + + {t("logout")} diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index b3d816925..535efca5d 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -101,6 +101,24 @@ "welcome_description": "Create your first search space to start organizing your knowledge, connecting sources, and chatting with AI.", "create_first_button": "Create your first search space" }, + "userSettings": { + "title": "User Settings", + "description": "Manage your account settings and API access", + "back_to_app": "Back to app", + "footer": "User Settings", + "api_key_nav_label": "API Key", + "api_key_nav_description": "Manage your API access token", + "api_key_title": "API Key", + "api_key_description": "Use this key to authenticate API requests", + "api_key_warning_title": "Keep it secret", + "api_key_warning_description": "Your API key grants full access to your account. Never share it publicly or commit it to version control.", + "your_api_key": "Your API Key", + "copied": "Copied!", + "copy": "Copy to clipboard", + "no_api_key": "No API key found", + "usage_title": "How to use", + "usage_description": "Include your API key in the Authorization header:" + }, "dashboard": { "title": "Dashboard", "search_spaces": "Search Spaces", @@ -658,6 +676,7 @@ "see_all_search_spaces": "See all search spaces", "expand_sidebar": "Expand sidebar", "collapse_sidebar": "Collapse sidebar", + "user_settings": "User settings", "logout": "Logout" }, "errors": { diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index 678aa174a..f7ee458e4 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -101,6 +101,24 @@ "welcome_description": "创建您的第一个搜索空间,开始组织知识、连接数据源并与AI对话。", "create_first_button": "创建第一个搜索空间" }, + "userSettings": { + "title": "用户设置", + "description": "管理您的账户设置和API访问", + "back_to_app": "返回应用", + "footer": "用户设置", + "api_key_nav_label": "API密钥", + "api_key_nav_description": "管理您的API访问令牌", + "api_key_title": "API密钥", + "api_key_description": "使用此密钥验证API请求", + "api_key_warning_title": "请保密", + "api_key_warning_description": "您的API密钥可以完全访问您的账户。请勿公开分享或提交到版本控制。", + "your_api_key": "您的API密钥", + "copied": "已复制!", + "copy": "复制到剪贴板", + "no_api_key": "未找到API密钥", + "usage_title": "使用方法", + "usage_description": "在Authorization请求头中包含您的API密钥:" + }, "dashboard": { "title": "仪表盘", "search_spaces": "搜索空间", @@ -652,6 +670,7 @@ "see_all_search_spaces": "查看所有搜索空间", "expand_sidebar": "展开侧边栏", "collapse_sidebar": "收起侧边栏", + "user_settings": "用户设置", "logout": "退出登录" }, "errors": {