diff --git a/surfsense_web/app/dashboard/[search_space_id]/api-key/api-key-client.tsx b/surfsense_web/app/dashboard/api-key/api-key-client.tsx similarity index 100% rename from surfsense_web/app/dashboard/[search_space_id]/api-key/api-key-client.tsx rename to surfsense_web/app/dashboard/api-key/api-key-client.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/api-key/client-wrapper.tsx b/surfsense_web/app/dashboard/api-key/client-wrapper.tsx similarity index 100% rename from surfsense_web/app/dashboard/[search_space_id]/api-key/client-wrapper.tsx rename to surfsense_web/app/dashboard/api-key/client-wrapper.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/api-key/page.tsx b/surfsense_web/app/dashboard/api-key/page.tsx similarity index 100% rename from surfsense_web/app/dashboard/[search_space_id]/api-key/page.tsx rename to surfsense_web/app/dashboard/api-key/page.tsx diff --git a/surfsense_web/app/dashboard/page.tsx b/surfsense_web/app/dashboard/page.tsx index 176c9bf..c0ca562 100644 --- a/surfsense_web/app/dashboard/page.tsx +++ b/surfsense_web/app/dashboard/page.tsx @@ -1,14 +1,15 @@ "use client"; -import React from 'react' +import React, { useEffect, useState } from 'react' import Link from 'next/link' import { motion } from 'framer-motion' import { Button } from '@/components/ui/button' -import { Plus, Search, Trash2, AlertCircle, Loader2, LogOut } from 'lucide-react' +import { Plus, Search, Trash2, AlertCircle, Loader2 } from 'lucide-react' import { Tilt } from '@/components/ui/tilt' import { Spotlight } from '@/components/ui/spotlight' import { Logo } from '@/components/Logo'; import { ThemeTogglerComponent } from '@/components/theme/theme-toggle'; +import { UserDropdown } from '@/components/UserDropdown'; import { toast } from 'sonner'; import { AlertDialog, @@ -28,8 +29,17 @@ import { } from "@/components/ui/alert"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { useSearchSpaces } from '@/hooks/use-search-spaces'; +import { apiClient } from '@/lib/api'; import { useRouter } from 'next/navigation'; +interface User { + id: string; + email: string; + is_active: boolean; + is_superuser: boolean; + is_verified: boolean; +} + /** * Formats a date string into a readable format * @param dateString - The date string to format @@ -147,17 +157,47 @@ const DashboardPage = () => { const router = useRouter(); const { searchSpaces, loading, error, refreshSearchSpaces } = useSearchSpaces(); + + // User state management + const [user, setUser] = useState(null); + const [isLoadingUser, setIsLoadingUser] = useState(true); + const [userError, setUserError] = useState(null); + + // Fetch user details + useEffect(() => { + const fetchUser = async () => { + try { + if (typeof window === 'undefined') return; + + try { + const userData = await apiClient.get('users/me'); + setUser(userData); + setUserError(null); + } catch (error) { + console.error('Error fetching user:', error); + setUserError(error instanceof Error ? error.message : 'Unknown error occurred'); + } finally { + setIsLoadingUser(false); + } + } catch (error) { + console.error('Error in fetchUser:', error); + setIsLoadingUser(false); + } + }; + + fetchUser(); + }, []); + + // Create user object for UserDropdown + const customUser = { + name: user?.email ? user.email.split('@')[0] : 'User', + email: user?.email || (isLoadingUser ? 'Loading...' : userError ? 'Error loading user' : 'Unknown User'), + avatar: '/icon-128.png', // Default avatar + }; if (loading) return ; if (error) return ; - const handleLogout = () => { - if (typeof window !== 'undefined') { - localStorage.removeItem('surfsense_bearer_token'); - router.push('/'); - } - }; - const handleDeleteSearchSpace = async (id: number) => { // Send DELETE request to the API try { @@ -201,18 +241,10 @@ const DashboardPage = () => {

-
- - -
+
+ + +
diff --git a/surfsense_web/components/UserDropdown.tsx b/surfsense_web/components/UserDropdown.tsx new file mode 100644 index 0000000..30ac879 --- /dev/null +++ b/surfsense_web/components/UserDropdown.tsx @@ -0,0 +1,92 @@ +"use client" + +import { + BadgeCheck, + ChevronsUpDown, + LogOut, + Settings, +} from "lucide-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Button } from "@/components/ui/button" +import { useRouter, useParams } from "next/navigation" + +export function UserDropdown({ + user, +}: { + user: { + name: string + email: string + avatar: string + } +}) { + const router = useRouter() + + const handleLogout = () => { + if (typeof window !== 'undefined') { + localStorage.removeItem('surfsense_bearer_token'); + router.push('/'); + } + }; + + return ( + + + + + + +
+

{user.name}

+

+ {user.email} +

+
+
+ + + + router.push(`/dashboard/api-key`)}> + + API Key + + + + + router.push(`/settings`)}> + + Settings + + + + Log out + +
+
+ ) +} \ No newline at end of file diff --git a/surfsense_web/components/sidebar/AppSidebarProvider.tsx b/surfsense_web/components/sidebar/AppSidebarProvider.tsx index 9859328..0039d3b 100644 --- a/surfsense_web/components/sidebar/AppSidebarProvider.tsx +++ b/surfsense_web/components/sidebar/AppSidebarProvider.tsx @@ -31,14 +31,6 @@ interface SearchSpace { user_id: string; } -interface User { - id: string; - email: string; - is_active: boolean; - is_superuser: boolean; - is_verified: boolean; -} - interface AppSidebarProviderProps { searchSpaceId: string; navSecondary: { @@ -58,20 +50,17 @@ interface AppSidebarProviderProps { }[]; } -export function AppSidebarProvider({ - searchSpaceId, - navSecondary, - navMain +export function AppSidebarProvider({ + searchSpaceId, + navSecondary, + navMain }: AppSidebarProviderProps) { const [recentChats, setRecentChats] = useState<{ name: string; url: string; icon: string; id: number; search_space_id: number; actions: { name: string; icon: string; onClick: () => void }[] }[]>([]); const [searchSpace, setSearchSpace] = useState(null); - const [user, setUser] = useState(null); const [isLoadingChats, setIsLoadingChats] = useState(true); const [isLoadingSearchSpace, setIsLoadingSearchSpace] = useState(true); - const [isLoadingUser, setIsLoadingUser] = useState(true); const [chatError, setChatError] = useState(null); const [searchSpaceError, setSearchSpaceError] = useState(null); - const [userError, setUserError] = useState(null); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [chatToDelete, setChatToDelete] = useState<{ id: number, name: string } | null>(null); const [isDeleting, setIsDeleting] = useState(false); @@ -82,33 +71,6 @@ export function AppSidebarProvider({ setIsClient(true); }, []); - // Fetch user details - useEffect(() => { - const fetchUser = async () => { - try { - // Only run on client-side - if (typeof window === 'undefined') return; - - try { - // Use the API client instead of direct fetch - const userData = await apiClient.get('users/me'); - setUser(userData); - setUserError(null); - } catch (error) { - console.error('Error fetching user:', error); - setUserError(error instanceof Error ? error.message : 'Unknown error occurred'); - } finally { - setIsLoadingUser(false); - } - } catch (error) { - console.error('Error in fetchUser:', error); - setIsLoadingUser(false); - } - }; - - fetchUser(); - }, []); - // Fetch recent chats useEffect(() => { const fetchRecentChats = async () => { @@ -119,9 +81,9 @@ export function AppSidebarProvider({ try { // Use the API client instead of direct fetch - filter by current search space ID const chats: Chat[] = await apiClient.get(`api/v1/chats/?limit=5&skip=0&search_space_id=${searchSpaceId}`); - + // Sort chats by created_at in descending order (newest first) - const sortedChats = chats.sort((a, b) => + const sortedChats = chats.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime() ); // console.log("sortedChats", sortedChats); @@ -171,7 +133,7 @@ export function AppSidebarProvider({ // Set up a refresh interval (every 5 minutes) const intervalId = setInterval(fetchRecentChats, 5 * 60 * 1000); - + // Clean up interval on component unmount return () => clearInterval(intervalId); }, [searchSpaceId]); @@ -179,16 +141,16 @@ export function AppSidebarProvider({ // Handle delete chat const handleDeleteChat = async () => { if (!chatToDelete) return; - + try { setIsDeleting(true); - + // Use the API client instead of direct fetch await apiClient.delete(`api/v1/chats/${chatToDelete.id}`); - + // Close dialog and refresh chats setRecentChats(recentChats.filter(chat => chat.id !== chatToDelete.id)); - + } catch (error) { console.error('Error deleting chat:', error); } finally { @@ -226,15 +188,15 @@ export function AppSidebarProvider({ }, [searchSpaceId]); // Create a fallback chat if there's an error or no chats - const fallbackChats = chatError || (!isLoadingChats && recentChats.length === 0) - ? [{ - name: chatError ? "Error loading chats" : "No recent chats", - url: "#", - icon: chatError ? "AlertCircle" : "MessageCircleMore", - id: 0, - search_space_id: Number(searchSpaceId), - actions: [] - }] + const fallbackChats = chatError || (!isLoadingChats && recentChats.length === 0) + ? [{ + name: chatError ? "Error loading chats" : "No recent chats", + url: "#", + icon: chatError ? "AlertCircle" : "MessageCircleMore", + id: 0, + search_space_id: Number(searchSpaceId), + actions: [] + }] : []; // Use fallback chats if there's an error or no chats @@ -249,22 +211,14 @@ export function AppSidebarProvider({ }; } - // Create user object for AppSidebar - const customUser = { - name: isClient && user?.email ? user.email.split('@')[0] : 'User', - email: isClient ? (user?.email || (isLoadingUser ? 'Loading...' : userError ? 'Error loading user' : 'Unknown User')) : 'Loading...', - avatar: '/icon-128.png', // Default avatar - }; - return ( <> - + {/* Delete Confirmation Dialog - Only render on client */} {isClient && ( diff --git a/surfsense_web/components/sidebar/app-sidebar.tsx b/surfsense_web/components/sidebar/app-sidebar.tsx index cd437f3..6b9cd9e 100644 --- a/surfsense_web/components/sidebar/app-sidebar.tsx +++ b/surfsense_web/components/sidebar/app-sidebar.tsx @@ -23,7 +23,6 @@ import { Logo } from "@/components/Logo"; import { NavMain } from "@/components/sidebar/nav-main" import { NavProjects } from "@/components/sidebar/nav-projects" import { NavSecondary } from "@/components/sidebar/nav-secondary" -import { NavUser } from "@/components/sidebar/nav-user" import { Sidebar, SidebarContent, @@ -143,11 +142,6 @@ const defaultData = { } interface AppSidebarProps extends React.ComponentProps { - user?: { - name: string - email: string - avatar: string - } navMain?: { title: string url: string @@ -178,7 +172,6 @@ interface AppSidebarProps extends React.ComponentProps { } export function AppSidebar({ - user = defaultData.user, navMain = defaultData.navMain, navSecondary = defaultData.navSecondary, RecentChats = defaultData.RecentChats, @@ -232,9 +225,9 @@ export function AppSidebar({ {processedRecentChats.length > 0 && } - - - + {/* + footer + */} ) }