diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx index 3dad836..08cbb9b 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/Dashboard.tsx @@ -1,284 +1,10 @@ -import { useState, useEffect } from 'react' -import { Toaster } from '@/components/ui/sonner' -import { toast } from 'sonner' -import { useAuth } from '../contexts/AuthContext' -import { RepoList } from './RepoList' -import { AddRepoForm } from './AddRepoForm' -import { SearchPanel } from './SearchPanel' -import { DependencyGraph } from './DependencyGraph' -import { RepoOverview } from './RepoOverview' -import { StyleInsights } from './StyleInsights' -import { ImpactAnalyzer } from './ImpactAnalyzer' -import { PerformanceDashboard } from './PerformanceDashboard' -import { UserNav } from './UserNav' -import type { Repository } from '../types' -import { API_URL } from '../config/api' - -type RepoTab = 'overview' | 'search' | 'dependencies' | 'insights' | 'impact' +import { DashboardLayout } from './dashboard/DashboardLayout' +import { DashboardHome } from './dashboard/DashboardHome' export function Dashboard() { - const { session } = useAuth() - const [repos, setRepos] = useState([]) - const [selectedRepo, setSelectedRepo] = useState(null) - const [activeTab, setActiveTab] = useState('overview') - const [loading, setLoading] = useState(false) - const [showPerformance, setShowPerformance] = useState(false) - - const fetchRepos = async () => { - if (!session?.access_token) return - - try { - const response = await fetch(`${API_URL}/api/repos`, { - headers: { 'Authorization': `Bearer ${session.access_token}` } - }) - const data = await response.json() - setRepos(data.repositories || []) - } catch (error) { - console.error('Error fetching repos:', error) - } - } - - useEffect(() => { - fetchRepos() - const interval = setInterval(fetchRepos, 30000) - return () => clearInterval(interval) - }, []) - - const handleAddRepo = async (gitUrl: string, branch: string) => { - try { - setLoading(true) - const name = gitUrl.split('/').pop()?.replace('.git', '') || 'unknown' - - const response = await fetch(`${API_URL}/api/repos`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${session.access_token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ name, git_url: gitUrl, branch }) - }) - - const data = await response.json() - - await fetch(`${API_URL}/api/repos/${data.repo_id}/index`, { - method: 'POST', - headers: { 'Authorization': `Bearer ${session.access_token}` } - }) - - await fetchRepos() - toast.success('Repository added!', { - description: `${name} is now being indexed` - }) - } catch (error) { - console.error('Error adding repo:', error) - toast.error('Failed to add repository', { - description: 'Please check the Git URL and try again' - }) - } finally { - setLoading(false) - } - } - - const handleReindex = async () => { - if (!selectedRepo) return - - try { - setLoading(true) - await fetch(`${API_URL}/api/repos/${selectedRepo}/index`, { - method: 'POST', - headers: { 'Authorization': `Bearer ${session.access_token}` } - }) - await fetchRepos() - // RepoOverview component will show the toast and progress - } catch (error) { - toast.error('Re-indexing failed', { - description: 'Please check the console for details' - }) - } finally { - setLoading(false) - } - } - - const selectedRepoData = repos.find(r => r.id === selectedRepo) - const isRepoView = selectedRepo && selectedRepoData - return ( -
- - -
- {/* Performance Dashboard Overlay */} - {showPerformance && ( -
- -
- )} - - {!isRepoView && ( -
-
-
-

Repositories

-

Manage and index your codebases

-
- -
- - { - setSelectedRepo(id) - setActiveTab('overview') - }} - /> -
- )} - - {isRepoView && ( -
-
- - -
-
-

{selectedRepoData.name}

-

{selectedRepoData.git_url}

-
- - {selectedRepoData.status === 'indexed' && ( - ✓ Indexed - )} -
-
- -
-
- - - - - -
-
- - {activeTab === 'overview' && ( - - )} - - {activeTab === 'search' && ( - - )} - - {activeTab === 'dependencies' && ( - - )} - - {activeTab === 'insights' && ( - - )} - - {activeTab === 'impact' && ( - - )} -
- )} -
- -
+ + + ) } diff --git a/frontend/src/components/dashboard/DashboardHome.tsx b/frontend/src/components/dashboard/DashboardHome.tsx new file mode 100644 index 0000000..47ba2be --- /dev/null +++ b/frontend/src/components/dashboard/DashboardHome.tsx @@ -0,0 +1,247 @@ +import { useState, useEffect } from 'react' +import { toast } from 'sonner' +import { useAuth } from '../../contexts/AuthContext' +import { RepoList } from '../RepoList' +import { AddRepoForm } from '../AddRepoForm' +import { SearchPanel } from '../SearchPanel' +import { DependencyGraph } from '../DependencyGraph' +import { RepoOverview } from '../RepoOverview' +import { StyleInsights } from '../StyleInsights' +import { ImpactAnalyzer } from '../ImpactAnalyzer' +import { PerformanceDashboard } from '../PerformanceDashboard' +import type { Repository } from '../../types' +import { API_URL } from '../../config/api' + +type RepoTab = 'overview' | 'search' | 'dependencies' | 'insights' | 'impact' + +export function DashboardHome() { + const { session } = useAuth() + const [repos, setRepos] = useState([]) + const [selectedRepo, setSelectedRepo] = useState(null) + const [activeTab, setActiveTab] = useState('overview') + const [loading, setLoading] = useState(false) + const [showPerformance, setShowPerformance] = useState(false) + + const fetchRepos = async () => { + if (!session?.access_token) return + + try { + const response = await fetch(`${API_URL}/api/repos`, { + headers: { 'Authorization': `Bearer ${session.access_token}` } + }) + const data = await response.json() + setRepos(data.repositories || []) + } catch (error) { + console.error('Error fetching repos:', error) + } + } + + useEffect(() => { + fetchRepos() + const interval = setInterval(fetchRepos, 30000) + return () => clearInterval(interval) + }, [session]) + + const handleAddRepo = async (gitUrl: string, branch: string) => { + try { + setLoading(true) + const name = gitUrl.split('/').pop()?.replace('.git', '') || 'unknown' + + const response = await fetch(`${API_URL}/api/repos`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${session?.access_token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ name, git_url: gitUrl, branch }) + }) + + const data = await response.json() + + await fetch(`${API_URL}/api/repos/${data.repo_id}/index`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${session?.access_token}` } + }) + + await fetchRepos() + toast.success('Repository added!', { + description: `${name} is now being indexed` + }) + } catch (error) { + console.error('Error adding repo:', error) + toast.error('Failed to add repository', { + description: 'Please check the Git URL and try again' + }) + } finally { + setLoading(false) + } + } + + const handleReindex = async () => { + if (!selectedRepo) return + + try { + setLoading(true) + await fetch(`${API_URL}/api/repos/${selectedRepo}/index`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${session?.access_token}` } + }) + await fetchRepos() + } catch (error) { + toast.error('Re-indexing failed', { + description: 'Please check the console for details' + }) + } finally { + setLoading(false) + } + } + + const selectedRepoData = repos.find(r => r.id === selectedRepo) + const isRepoView = selectedRepo && selectedRepoData + + // Tab button component + const TabButton = ({ tab, label }: { tab: RepoTab; label: string }) => ( + + ) + + return ( +
+ {/* Performance Dashboard Toggle */} + {showPerformance && ( +
+ +
+ )} + + {/* Repository List View */} + {!isRepoView && ( +
+
+
+

Repositories

+

+ {repos.length} repositories • {repos.filter(r => r.status === 'indexed').length} indexed +

+
+
+ + +
+
+ + { + setSelectedRepo(id) + setActiveTab('overview') + }} + /> +
+ )} + + {/* Single Repo View */} + {isRepoView && ( +
+ {/* Back Button & Header */} +
+ + +
+
+

{selectedRepoData.name}

+

{selectedRepoData.git_url}

+
+ + {selectedRepoData.status === 'indexed' && ( + + ✓ Indexed + + )} +
+
+ + {/* Tabs */} +
+ + + + + +
+ + {/* Tab Content */} +
+ {activeTab === 'overview' && ( + + )} + + {activeTab === 'search' && ( + + )} + + {activeTab === 'dependencies' && ( + + )} + + {activeTab === 'insights' && ( + + )} + + {activeTab === 'impact' && ( + + )} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/dashboard/DashboardLayout.tsx b/frontend/src/components/dashboard/DashboardLayout.tsx new file mode 100644 index 0000000..d272b12 --- /dev/null +++ b/frontend/src/components/dashboard/DashboardLayout.tsx @@ -0,0 +1,54 @@ +import { useState } from 'react' +import { Outlet } from 'react-router-dom' +import { Sidebar } from './Sidebar' +import { TopNav } from './TopNav' +import { Toaster } from '@/components/ui/sonner' + +interface DashboardLayoutProps { + children?: React.ReactNode +} + +export function DashboardLayout({ children }: DashboardLayoutProps) { + const [sidebarCollapsed, setSidebarCollapsed] = useState(false) + + return ( +
+ {/* Top Navigation */} + setSidebarCollapsed(!sidebarCollapsed)} + sidebarCollapsed={sidebarCollapsed} + /> + +
+ {/* Sidebar */} + setSidebarCollapsed(!sidebarCollapsed)} + /> + + {/* Main Content */} +
+
+ {children || } +
+
+
+ + +
+ ) +} diff --git a/frontend/src/components/dashboard/Sidebar.tsx b/frontend/src/components/dashboard/Sidebar.tsx new file mode 100644 index 0000000..8aede9f --- /dev/null +++ b/frontend/src/components/dashboard/Sidebar.tsx @@ -0,0 +1,121 @@ +import { Link, useLocation } from 'react-router-dom' +import { useAuth } from '../../contexts/AuthContext' + +interface SidebarProps { + collapsed: boolean + onToggle: () => void +} + +// Icons +const RepoIcon = () => ( + + + +) + +const SearchIcon = () => ( + + + +) + +const SettingsIcon = () => ( + + + + +) + +const ChevronIcon = ({ direction = 'left' }: { direction?: 'left' | 'right' }) => ( + + + +) + +const DocsIcon = () => ( + + + +) + +interface NavItem { + name: string + href: string + icon: React.ReactNode +} + +const mainNavItems: NavItem[] = [ + { name: 'Repositories', href: '/dashboard', icon: }, + { name: 'Global Search', href: '/dashboard/search', icon: }, +] + +const bottomNavItems: NavItem[] = [ + { name: 'Documentation', href: '/docs', icon: }, + { name: 'Settings', href: '/dashboard/settings', icon: }, +] + +export function Sidebar({ collapsed, onToggle }: SidebarProps) { + const location = useLocation() + const { session } = useAuth() + + const isActive = (href: string) => { + if (href === '/dashboard') { + return location.pathname === '/dashboard' || location.pathname.startsWith('/dashboard/repo/') + } + return location.pathname === href + } + + const NavLink = ({ item }: { item: NavItem }) => ( + + + {item.icon} + + {!collapsed && ( + {item.name} + )} + + ) + + return ( + + ) +} diff --git a/frontend/src/components/dashboard/TopNav.tsx b/frontend/src/components/dashboard/TopNav.tsx new file mode 100644 index 0000000..fe373e5 --- /dev/null +++ b/frontend/src/components/dashboard/TopNav.tsx @@ -0,0 +1,145 @@ +import { Link } from 'react-router-dom' +import { useAuth } from '../../contexts/AuthContext' +import { useState } from 'react' + +interface TopNavProps { + onToggleSidebar: () => void + sidebarCollapsed: boolean +} + +// Icons +const MenuIcon = () => ( + + + +) + +const SearchIcon = () => ( + + + +) + +const GitHubIcon = () => ( + + + +) + +const CodeIntelLogo = () => ( +
+ CI +
+) + +export function TopNav({ onToggleSidebar, sidebarCollapsed }: TopNavProps) { + const { session, signOut } = useAuth() + const [showUserMenu, setShowUserMenu] = useState(false) + + const userEmail = session?.user?.email || 'User' + const userInitial = userEmail.charAt(0).toUpperCase() + + return ( + + ) +} diff --git a/frontend/src/components/dashboard/index.ts b/frontend/src/components/dashboard/index.ts new file mode 100644 index 0000000..5fa7d0c --- /dev/null +++ b/frontend/src/components/dashboard/index.ts @@ -0,0 +1,4 @@ +export { DashboardLayout } from './DashboardLayout' +export { Sidebar } from './Sidebar' +export { TopNav } from './TopNav' +export { DashboardHome } from './DashboardHome'