Skip to content

Commit a5daa23

Browse files
committed
feat(frontend): Dashboard Layout + Sidebar + TopNav (#32)
- Add DashboardLayout with dark theme (#09090b) - Add collapsible Sidebar (64px collapsed, 240px expanded) - Add TopNav with logo, search placeholder, user menu - Refactor Dashboard to use new layout components - Smooth collapse animations - Mobile responsive structure
1 parent c1a65a5 commit a5daa23

6 files changed

Lines changed: 576 additions & 279 deletions

File tree

Lines changed: 5 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -1,284 +1,10 @@
1-
import { useState, useEffect } from 'react'
2-
import { Toaster } from '@/components/ui/sonner'
3-
import { toast } from 'sonner'
4-
import { useAuth } from '../contexts/AuthContext'
5-
import { RepoList } from './RepoList'
6-
import { AddRepoForm } from './AddRepoForm'
7-
import { SearchPanel } from './SearchPanel'
8-
import { DependencyGraph } from './DependencyGraph'
9-
import { RepoOverview } from './RepoOverview'
10-
import { StyleInsights } from './StyleInsights'
11-
import { ImpactAnalyzer } from './ImpactAnalyzer'
12-
import { PerformanceDashboard } from './PerformanceDashboard'
13-
import { UserNav } from './UserNav'
14-
import type { Repository } from '../types'
15-
import { API_URL } from '../config/api'
16-
17-
type RepoTab = 'overview' | 'search' | 'dependencies' | 'insights' | 'impact'
1+
import { DashboardLayout } from './dashboard/DashboardLayout'
2+
import { DashboardHome } from './dashboard/DashboardHome'
183

194
export function Dashboard() {
20-
const { session } = useAuth()
21-
const [repos, setRepos] = useState<Repository[]>([])
22-
const [selectedRepo, setSelectedRepo] = useState<string | null>(null)
23-
const [activeTab, setActiveTab] = useState<RepoTab>('overview')
24-
const [loading, setLoading] = useState(false)
25-
const [showPerformance, setShowPerformance] = useState(false)
26-
27-
const fetchRepos = async () => {
28-
if (!session?.access_token) return
29-
30-
try {
31-
const response = await fetch(`${API_URL}/api/repos`, {
32-
headers: { 'Authorization': `Bearer ${session.access_token}` }
33-
})
34-
const data = await response.json()
35-
setRepos(data.repositories || [])
36-
} catch (error) {
37-
console.error('Error fetching repos:', error)
38-
}
39-
}
40-
41-
useEffect(() => {
42-
fetchRepos()
43-
const interval = setInterval(fetchRepos, 30000)
44-
return () => clearInterval(interval)
45-
}, [])
46-
47-
const handleAddRepo = async (gitUrl: string, branch: string) => {
48-
try {
49-
setLoading(true)
50-
const name = gitUrl.split('/').pop()?.replace('.git', '') || 'unknown'
51-
52-
const response = await fetch(`${API_URL}/api/repos`, {
53-
method: 'POST',
54-
headers: {
55-
'Authorization': `Bearer ${session.access_token}`,
56-
'Content-Type': 'application/json'
57-
},
58-
body: JSON.stringify({ name, git_url: gitUrl, branch })
59-
})
60-
61-
const data = await response.json()
62-
63-
await fetch(`${API_URL}/api/repos/${data.repo_id}/index`, {
64-
method: 'POST',
65-
headers: { 'Authorization': `Bearer ${session.access_token}` }
66-
})
67-
68-
await fetchRepos()
69-
toast.success('Repository added!', {
70-
description: `${name} is now being indexed`
71-
})
72-
} catch (error) {
73-
console.error('Error adding repo:', error)
74-
toast.error('Failed to add repository', {
75-
description: 'Please check the Git URL and try again'
76-
})
77-
} finally {
78-
setLoading(false)
79-
}
80-
}
81-
82-
const handleReindex = async () => {
83-
if (!selectedRepo) return
84-
85-
try {
86-
setLoading(true)
87-
await fetch(`${API_URL}/api/repos/${selectedRepo}/index`, {
88-
method: 'POST',
89-
headers: { 'Authorization': `Bearer ${session.access_token}` }
90-
})
91-
await fetchRepos()
92-
// RepoOverview component will show the toast and progress
93-
} catch (error) {
94-
toast.error('Re-indexing failed', {
95-
description: 'Please check the console for details'
96-
})
97-
} finally {
98-
setLoading(false)
99-
}
100-
}
101-
102-
const selectedRepoData = repos.find(r => r.id === selectedRepo)
103-
const isRepoView = selectedRepo && selectedRepoData
104-
1055
return (
106-
<div className="min-h-screen bg-gray-50">
107-
<nav className="bg-white border-b border-gray-200">
108-
<div className="max-w-7xl mx-auto px-6">
109-
<div className="flex items-center justify-between h-16">
110-
<div className="flex items-center gap-2">
111-
<div className="w-8 h-8 rounded-lg bg-blue-600 flex items-center justify-center">
112-
<span className="text-white font-bold text-sm">CI</span>
113-
</div>
114-
<span className="font-semibold text-gray-900">CodeIntel</span>
115-
<span className="text-xs text-gray-400 ml-2">MCP Server</span>
116-
</div>
117-
118-
<div className="flex items-center gap-4">
119-
<button
120-
onClick={() => setShowPerformance(!showPerformance)}
121-
className={`text-xs px-3 py-1.5 rounded-md transition-colors ${
122-
showPerformance
123-
? 'bg-blue-100 text-blue-700 font-medium'
124-
: 'text-gray-600 hover:bg-gray-100'
125-
}`}
126-
>
127-
📊 Performance
128-
</button>
129-
<div className="flex items-center gap-2 text-xs text-gray-500">
130-
<div className="w-2 h-2 rounded-full bg-green-500" />
131-
<span>API Connected</span>
132-
</div>
133-
<div className="text-xs text-gray-400 border-l border-gray-200 pl-4">
134-
{repos.length} repos • {repos.filter(r => r.status === 'indexed').length} indexed
135-
</div>
136-
<div className="border-l border-gray-200 pl-4">
137-
<UserNav />
138-
</div>
139-
</div>
140-
</div>
141-
</div>
142-
</nav>
143-
144-
<main className="max-w-7xl mx-auto px-6 py-8">
145-
{/* Performance Dashboard Overlay */}
146-
{showPerformance && (
147-
<div className="mb-6">
148-
<PerformanceDashboard apiUrl={API_URL} apiKey={session.access_token} />
149-
</div>
150-
)}
151-
152-
{!isRepoView && (
153-
<div className="space-y-6">
154-
<div className="flex items-center justify-between">
155-
<div>
156-
<h1 className="text-2xl font-semibold text-gray-900">Repositories</h1>
157-
<p className="text-sm text-gray-600 mt-1">Manage and index your codebases</p>
158-
</div>
159-
<AddRepoForm onAdd={handleAddRepo} loading={loading} />
160-
</div>
161-
162-
<RepoList
163-
repos={repos}
164-
selectedRepo={selectedRepo}
165-
onSelect={(id) => {
166-
setSelectedRepo(id)
167-
setActiveTab('overview')
168-
}}
169-
/>
170-
</div>
171-
)}
172-
173-
{isRepoView && (
174-
<div>
175-
<div className="mb-6">
176-
<button
177-
onClick={() => {
178-
setSelectedRepo(null)
179-
setActiveTab('overview')
180-
}}
181-
className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 mb-4"
182-
>
183-
<span></span>
184-
<span>Back to Repositories</span>
185-
</button>
186-
187-
<div className="flex items-center justify-between">
188-
<div>
189-
<h1 className="text-2xl font-semibold text-gray-900">{selectedRepoData.name}</h1>
190-
<p className="text-sm text-gray-600 mt-1 font-mono">{selectedRepoData.git_url}</p>
191-
</div>
192-
193-
{selectedRepoData.status === 'indexed' && (
194-
<span className="badge-success">✓ Indexed</span>
195-
)}
196-
</div>
197-
</div>
198-
199-
<div className="border-b border-gray-200 mb-6">
200-
<div className="flex gap-6 overflow-x-auto">
201-
<button
202-
onClick={() => setActiveTab('overview')}
203-
className={`pb-3 border-b-2 transition-colors whitespace-nowrap ${
204-
activeTab === 'overview'
205-
? 'border-blue-600 text-gray-900 font-medium'
206-
: 'border-transparent text-gray-600 hover:text-gray-900'
207-
}`}
208-
>
209-
Overview
210-
</button>
211-
<button
212-
onClick={() => setActiveTab('search')}
213-
className={`pb-3 border-b-2 transition-colors whitespace-nowrap ${
214-
activeTab === 'search'
215-
? 'border-blue-600 text-gray-900 font-medium'
216-
: 'border-transparent text-gray-600 hover:text-gray-900'
217-
}`}
218-
>
219-
Search
220-
</button>
221-
<button
222-
onClick={() => setActiveTab('dependencies')}
223-
className={`pb-3 border-b-2 transition-colors whitespace-nowrap ${
224-
activeTab === 'dependencies'
225-
? 'border-blue-600 text-gray-900 font-medium'
226-
: 'border-transparent text-gray-600 hover:text-gray-900'
227-
}`}
228-
>
229-
Dependencies
230-
</button>
231-
<button
232-
onClick={() => setActiveTab('insights')}
233-
className={`pb-3 border-b-2 transition-colors whitespace-nowrap ${
234-
activeTab === 'insights'
235-
? 'border-blue-600 text-gray-900 font-medium'
236-
: 'border-transparent text-gray-600 hover:text-gray-900'
237-
}`}
238-
>
239-
Code Style
240-
</button>
241-
<button
242-
onClick={() => setActiveTab('impact')}
243-
className={`pb-3 border-b-2 transition-colors whitespace-nowrap ${
244-
activeTab === 'impact'
245-
? 'border-blue-600 text-gray-900 font-medium'
246-
: 'border-transparent text-gray-600 hover:text-gray-900'
247-
}`}
248-
>
249-
Impact
250-
</button>
251-
</div>
252-
</div>
253-
254-
{activeTab === 'overview' && (
255-
<RepoOverview
256-
repo={selectedRepoData}
257-
onReindex={handleReindex}
258-
apiUrl={API_URL}
259-
apiKey={session.access_token}
260-
/>
261-
)}
262-
263-
{activeTab === 'search' && (
264-
<SearchPanel repoId={selectedRepo} apiUrl={API_URL} apiKey={session.access_token} />
265-
)}
266-
267-
{activeTab === 'dependencies' && (
268-
<DependencyGraph repoId={selectedRepo} apiUrl={API_URL} apiKey={session.access_token} />
269-
)}
270-
271-
{activeTab === 'insights' && (
272-
<StyleInsights repoId={selectedRepo} apiUrl={API_URL} apiKey={session.access_token} />
273-
)}
274-
275-
{activeTab === 'impact' && (
276-
<ImpactAnalyzer repoId={selectedRepo} apiUrl={API_URL} apiKey={session.access_token} />
277-
)}
278-
</div>
279-
)}
280-
</main>
281-
<Toaster />
282-
</div>
6+
<DashboardLayout>
7+
<DashboardHome />
8+
</DashboardLayout>
2839
)
28410
}

0 commit comments

Comments
 (0)