From 3ae2a309b99324b9af1882eacc9066ef763a309c Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 14:39:35 -0500 Subject: [PATCH 1/8] feat(dashboard): v2 redesign with bento grid, animations, premium UX - RepoList: bento grid layout with featured card (sorted by function count) - RepoList: mouse-following glow effect on hover - RepoList: animated progress bars showing % of total - RepoList: distinct styling for pending vs indexed repos - DashboardStats: animated number counters on load - DashboardStats: gradient glows and premium card styling - RepoOverview: bento stats grid with icons and gradients - AddRepoForm: framer-motion modal animations - DashboardHome: page transitions with AnimatePresence Design inspired by Linear, Vercel, and 2026 dashboard trends. --- frontend/src/components/AddRepoForm.tsx | 199 +++++----- frontend/src/components/RepoList.tsx | 349 +++++++++++++----- frontend/src/components/RepoOverview.tsx | 262 ++++++------- .../components/dashboard/DashboardHome.tsx | 291 +++++++-------- .../components/dashboard/DashboardStats.tsx | 127 +++++++ 5 files changed, 760 insertions(+), 468 deletions(-) create mode 100644 frontend/src/components/dashboard/DashboardStats.tsx diff --git a/frontend/src/components/AddRepoForm.tsx b/frontend/src/components/AddRepoForm.tsx index d160c60..5e44ddd 100644 --- a/frontend/src/components/AddRepoForm.tsx +++ b/frontend/src/components/AddRepoForm.tsx @@ -1,4 +1,5 @@ import { useState } from 'react' +import { motion, AnimatePresence } from 'framer-motion' interface AddRepoFormProps { onAdd: (gitUrl: string, branch: string) => Promise @@ -13,110 +14,126 @@ export function AddRepoForm({ onAdd, loading }: AddRepoFormProps) { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!gitUrl) return - await onAdd(gitUrl, branch) setGitUrl('') setBranch('main') setShowForm(false) } - if (!showForm) { - return ( - - ) - } + + + Add Repository + - return ( -
-
- {/* Header */} -
-
-

Add Repository

-

Clone and index a Git repository

-
- -
+ e.stopPropagation()} + className="bg-gradient-to-br from-[#111113] to-[#0a0a0c] border border-white/10 rounded-2xl shadow-2xl w-full max-w-md mx-4" + > + {/* Header */} +
+
+
+ 📦 +
+
+

Add Repository

+

Clone and index with AI

+
+
+ +
- {/* Form */} -
-
- - setGitUrl(e.target.value)} - placeholder="https://github.com/username/repo" - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 transition-all" - required - disabled={loading} - autoFocus - /> -
+ {/* Form */} + +
+ + setGitUrl(e.target.value)} + placeholder="https://github.com/username/repo" + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500/50 focus:ring-2 focus:ring-blue-500/20 transition-all" + required + disabled={loading} + autoFocus + /> +
-
- - setBranch(e.target.value)} - placeholder="main" - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 transition-all" - required - disabled={loading} - /> -

- The repository will be cloned and automatically indexed -

-
+
+ + setBranch(e.target.value)} + placeholder="main" + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500/50 focus:ring-2 focus:ring-blue-500/20 transition-all" + required + disabled={loading} + /> +

Repository will be cloned and automatically indexed

+
- {/* Actions */} -
- - -
-
-
-
+ {/* Actions */} +
+ + + {loading ? ( + <> +
+ Adding... + + ) : ( + <> + 🚀 + Add & Index + + )} + +
+ + + + )} + + ) } diff --git a/frontend/src/components/RepoList.tsx b/frontend/src/components/RepoList.tsx index 2b1b650..d8a5a0a 100644 --- a/frontend/src/components/RepoList.tsx +++ b/frontend/src/components/RepoList.tsx @@ -1,3 +1,5 @@ +import { useState, useRef, useMemo } from 'react' +import { motion } from 'framer-motion' import type { Repository } from '../types' import { RepoGridSkeleton } from './ui/Skeleton' @@ -8,40 +10,214 @@ interface RepoListProps { loading?: boolean } -// Status indicator with glow effect -const StatusIndicator = ({ status }: { status: string }) => { +const StatusBadge = ({ status }: { status: string }) => { const config = { - indexed: { color: 'green', label: 'Indexed', icon: '✓' }, - cloned: { color: 'blue', label: 'Ready', icon: '✓' }, - indexing: { color: 'yellow', label: 'Indexing', icon: '◌' }, - cloning: { color: 'yellow', label: 'Cloning', icon: '◌' }, - error: { color: 'red', label: 'Error', icon: '✗' }, - }[status] || { color: 'gray', label: status, icon: '•' } - - const colorClasses = { - green: 'bg-green-500/10 text-green-400 border-green-500/20', - blue: 'bg-blue-500/10 text-blue-400 border-blue-500/20', - yellow: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20', - red: 'bg-red-500/10 text-red-400 border-red-500/20', - gray: 'bg-white/5 text-gray-400 border-white/10', - }[config.color] - - const glowClasses = { - green: 'shadow-green-500/20', - blue: 'shadow-blue-500/20', - yellow: 'shadow-yellow-500/20', - red: 'shadow-red-500/20', - gray: '', - }[config.color] + indexed: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', border: 'border-emerald-500/20', label: 'Indexed', dot: 'bg-emerald-400' }, + cloned: { bg: 'bg-blue-500/10', text: 'text-blue-400', border: 'border-blue-500/20', label: 'Pending Index', dot: 'bg-blue-400' }, + indexing: { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/20', label: 'Indexing...', dot: 'bg-amber-400 animate-pulse' }, + cloning: { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/20', label: 'Cloning...', dot: 'bg-amber-400 animate-pulse' }, + error: { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/20', label: 'Error', dot: 'bg-red-400' }, + }[status] || { bg: 'bg-white/5', text: 'text-gray-400', border: 'border-white/10', label: status, dot: 'bg-gray-400' } return ( - - {config.icon} - {config.label} + + + {config.label} ) } +// Featured card - larger, more info +const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: { + repo: Repository + totalFunctions: number + onSelect: () => void +}) => { + const cardRef = useRef(null) + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) + const [isHovering, setIsHovering] = useState(false) + const percentage = totalFunctions > 0 ? Math.round((repo.file_count || 0) / totalFunctions * 100) : 0 + + const handleMouseMove = (e: React.MouseEvent) => { + if (!cardRef.current) return + const rect = cardRef.current.getBoundingClientRect() + setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }) + } + + return ( + setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className="group relative text-left rounded-2xl overflow-hidden h-full min-h-[280px] + bg-gradient-to-br from-[#0f1114] via-[#0d0f12] to-[#0a0c0f] + border border-white/[0.08] hover:border-blue-500/30 + hover:shadow-2xl hover:shadow-blue-500/10 + focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-7" + > + {/* Mouse-following glow */} + {isHovering && ( +
+ )} + + {/* Top accent line */} +
+ + {/* Content */} +
+ {/* Badge */} +
+ + ★ Most Functions + +
+ + {/* Header */} +
+
+ + + +
+ +
+ + {/* Title */} +
+

+ {repo.name} +

+

{repo.branch}

+
+ + {/* Stats */} +
+
+ Functions indexed + + {(repo.file_count || 0).toLocaleString()} + +
+ + {/* Progress bar */} + {totalFunctions > 0 && ( +
+
+ +
+

{percentage}% of total indexed functions

+
+ )} +
+ + {/* Hover CTA */} +
+ + Explore + +
+
+ + ) +} + +// Regular card - compact +const RepoCard = ({ repo, index, onSelect }: { + repo: Repository + index: number + onSelect: () => void +}) => { + const cardRef = useRef(null) + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) + const [isHovering, setIsHovering] = useState(false) + const isPending = repo.status === 'cloned' || repo.status === 'cloning' + + const handleMouseMove = (e: React.MouseEvent) => { + if (!cardRef.current) return + const rect = cardRef.current.getBoundingClientRect() + setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }) + } + + return ( + setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className={`group relative text-left rounded-2xl overflow-hidden h-full min-h-[140px] + border transition-all duration-300 + focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-5 + ${isPending + ? 'bg-gradient-to-br from-[#0f1114] to-[#0d0f12] border-dashed border-white/[0.08] hover:border-blue-500/30' + : 'bg-gradient-to-br from-[#0f1114] to-[#0d0f12] border-white/[0.08] hover:border-white/[0.15]' + } + hover:shadow-xl hover:shadow-blue-500/[0.06]`} + > + {/* Mouse-following glow */} + {isHovering && ( +
+ )} + + {/* Content */} +
+ {/* Header */} +
+
+ + + +
+ +
+ + {/* Title */} +
+

+ {repo.name} +

+

{repo.branch}

+
+ + {/* Stats */} +
+
+ Functions + + {(repo.file_count || 0).toLocaleString()} + +
+
+
+ + ) +} + export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListProps) { if (loading) { return @@ -49,88 +225,65 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro if (repos.length === 0) { return ( -
-
+ +
📦

No repositories yet

Add your first repository to start searching code semantically with AI

-
+ ) } - return ( -
- {repos.map((repo, index) => { - const isSelected = selectedRepo === repo.id - - return ( - - ) - })} + return ( +
+ {/* Featured repo - spans full width on mobile, left column on desktop */} + {featured && ( +
0 ? 'lg:row-span-2' : ''}> + onSelect(featured.id)} + /> +
+ )} + + {/* Rest of repos */} + {rest.map((repo, index) => ( + onSelect(repo.id)} + /> + ))}
) } diff --git a/frontend/src/components/RepoOverview.tsx b/frontend/src/components/RepoOverview.tsx index 24348c2..6160228 100644 --- a/frontend/src/components/RepoOverview.tsx +++ b/frontend/src/components/RepoOverview.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useRef } from 'react' +import { motion } from 'framer-motion' import { toast } from 'sonner' import { Progress } from '@/components/ui/progress' import type { Repository } from '../types' @@ -19,21 +20,39 @@ interface IndexProgress { progress_pct: number } +const StatCard = ({ label, value, icon, gradient, delay = 0 }: { + label: string + value: string | number + icon: string + gradient: string + delay?: number +}) => ( + +
+
+
+ {icon} + {label} +
+
{value}
+
+ +) + export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewProps) { const [indexing, setIndexing] = useState(false) const [progress, setProgress] = useState(null) const wsRef = useRef(null) const completedRef = useRef(false) - - // Cache invalidation hook const invalidateCache = useInvalidateRepoCache() useEffect(() => { - return () => { - if (wsRef.current) { - wsRef.current.close() - } - } + return () => { if (wsRef.current) wsRef.current.close() } }, []) const handleReindex = async () => { @@ -41,32 +60,19 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr setProgress({ files_processed: 0, functions_indexed: 0, total_files: 0, progress_pct: 0 }) completedRef.current = false - const wsUrl = `${WS_URL}/ws/index/${repo.id}?token=${apiKey}` - try { - const ws = new WebSocket(wsUrl) + const ws = new WebSocket(`${WS_URL}/ws/index/${repo.id}?token=${apiKey}`) wsRef.current = ws - - ws.onopen = () => { - toast.loading('Indexing started...', { id: 'reindex' }) - } - + ws.onopen = () => toast.loading('Indexing started...', { id: 'reindex' }) ws.onmessage = (event) => { const data = JSON.parse(event.data) - if (data.type === 'progress') { - setProgress({ - files_processed: data.files_processed, - functions_indexed: data.functions_indexed, - total_files: data.total_files, - progress_pct: data.progress_pct - }) + setProgress({ files_processed: data.files_processed, functions_indexed: data.functions_indexed, total_files: data.total_files, progress_pct: data.progress_pct }) } else if (data.type === 'complete') { completedRef.current = true toast.success(`Indexing complete! ${data.total_functions} functions indexed.`, { id: 'reindex' }) setIndexing(false) setProgress(null) - // Invalidate caches after re-index invalidateCache(repo.id) onReindex() } else if (data.type === 'error') { @@ -76,20 +82,8 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr setProgress(null) } } - - ws.onerror = () => { - if (!completedRef.current) { - toast.dismiss('reindex') - fallbackToHttp() - } - } - - ws.onclose = () => { - if (!completedRef.current) { - fallbackToHttp() - } - } - + ws.onerror = () => { if (!completedRef.current) { toast.dismiss('reindex'); fallbackToHttp() } } + ws.onclose = () => { if (!completedRef.current) fallbackToHttp() } } catch { fallbackToHttp() } @@ -97,26 +91,13 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr const fallbackToHttp = async () => { if (completedRef.current) return - toast.loading('Using fallback indexing...', { id: 'reindex' }) - try { await onReindex() toast.success('Re-indexing started!', { id: 'reindex' }) - let pct = 10 - const interval = setInterval(() => { - pct = Math.min(pct + 10, 90) - setProgress(prev => prev ? { ...prev, progress_pct: pct } : null) - }, 1000) - - setTimeout(() => { - clearInterval(interval) - setProgress(null) - setIndexing(false) - completedRef.current = true - }, 8000) - + const interval = setInterval(() => { pct = Math.min(pct + 10, 90); setProgress(prev => prev ? { ...prev, progress_pct: pct } : null) }, 1000) + setTimeout(() => { clearInterval(interval); setProgress(null); setIndexing(false); completedRef.current = true }, 8000) } catch { setIndexing(false) setProgress(null) @@ -124,93 +105,99 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr } } + const statusConfig = { + indexed: { label: 'Indexed', color: 'text-emerald-400', bg: 'from-emerald-500/10 to-green-500/10', icon: '✓' }, + cloned: { label: 'Ready', color: 'text-blue-400', bg: 'from-blue-500/10 to-cyan-500/10', icon: '✓' }, + indexing: { label: 'Indexing', color: 'text-amber-400', bg: 'from-amber-500/10 to-orange-500/10', icon: '⏳' }, + }[repo.status] || { label: repo.status, color: 'text-gray-400', bg: 'from-gray-500/10 to-gray-500/10', icon: '•' } + return (
- {/* Stats Cards */} + {/* Bento Stats Grid */}
-
-
Status
-
- {repo.status === 'indexed' && ( - - ✓ Indexed - - )} - {repo.status === 'cloned' && ( - - ✓ Ready - - )} - {repo.status === 'indexing' && ( - - 🔄 Indexing - - )} -
-
- -
-
Functions Indexed
-
- {repo.file_count?.toLocaleString() || 0} -
-
- -
-
Branch
-
- {repo.branch} -
-
+ + +
{/* Indexing Progress */} {indexing && progress && ( -
+
-

🔄 Indexing in Progress

+
+ +

Indexing in Progress

+
{progress.progress_pct}%
- -
+ +
Files: {progress.files_processed}/{progress.total_files || '?'} Functions: {progress.functions_indexed}
-
+
)} - {/* Repository Info */} -
-

Repository Details

- -
-
- Name: + {/* Repository Details */} + +

+ 📋 Repository Details +

+
+
+ Name {repo.name}
- -
- Git URL: - + - -
- Local Path: +
+ Local Path {repo.local_path}
-
+ {/* Actions */} -
-

Actions

+ +

+ Actions +

Re-indexing uses incremental mode - only processes changed files for 100x faster updates!

@@ -218,30 +205,47 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr
-
+
{/* Quick Guide */} -
-

💡 Quick Guide

-
    -
  • Search tab - Find code by meaning, not keywords
  • -
  • Dependencies tab - Visualize code architecture
  • -
  • Code Style tab - Analyze team coding patterns
  • -
  • Impact tab - See what breaks when you change a file
  • -
  • • Use with Claude Desktop via MCP for AI-powered code understanding
  • -
-
+ +

+ 💡 Quick Guide +

+
+ {[ + { icon: '🔍', title: 'Search', desc: 'Find code by meaning, not keywords' }, + { icon: '🔗', title: 'Dependencies', desc: 'Visualize code architecture' }, + { icon: '✨', title: 'Code Style', desc: 'Analyze team coding patterns' }, + { icon: '💥', title: 'Impact', desc: 'See what breaks when you change a file' }, + ].map(item => ( +
+ {item.icon} +
+ {item.title} + {item.desc} +
+
+ ))} +
+
) } diff --git a/frontend/src/components/dashboard/DashboardHome.tsx b/frontend/src/components/dashboard/DashboardHome.tsx index d96e7da..8f0a91d 100644 --- a/frontend/src/components/dashboard/DashboardHome.tsx +++ b/frontend/src/components/dashboard/DashboardHome.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react' +import { motion, AnimatePresence } from 'framer-motion' import { toast } from 'sonner' import { useAuth } from '../../contexts/AuthContext' import { RepoList } from '../RepoList' @@ -8,7 +9,7 @@ import { DependencyGraph } from '../DependencyGraph' import { RepoOverview } from '../RepoOverview' import { StyleInsights } from '../StyleInsights' import { ImpactAnalyzer } from '../ImpactAnalyzer' -import { PerformanceDashboard } from '../PerformanceDashboard' +import { DashboardStats } from './DashboardStats' import type { Repository } from '../../types' import { API_URL } from '../../config/api' @@ -21,11 +22,9 @@ export function DashboardHome() { const [activeTab, setActiveTab] = useState('overview') const [loading, setLoading] = useState(false) const [reposLoading, setReposLoading] = useState(true) - const [showPerformance, setShowPerformance] = useState(false) const fetchRepos = async () => { if (!session?.access_token) return - try { const response = await fetch(`${API_URL}/repos`, { headers: { 'Authorization': `Bearer ${session.access_token}` } @@ -49,7 +48,6 @@ export function DashboardHome() { try { setLoading(true) const name = gitUrl.split('/').pop()?.replace('.git', '') || 'unknown' - const response = await fetch(`${API_URL}/repos`, { method: 'POST', headers: { @@ -58,23 +56,16 @@ export function DashboardHome() { }, body: JSON.stringify({ name, git_url: gitUrl, branch }) }) - const data = await response.json() - await fetch(`${API_URL}/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` - }) + 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' - }) + toast.error('Failed to add repository', { description: 'Please check the Git URL and try again' }) } finally { setLoading(false) } @@ -82,7 +73,6 @@ export function DashboardHome() { const handleReindex = async () => { if (!selectedRepo) return - try { setLoading(true) await fetch(`${API_URL}/repos/${selectedRepo}/index`, { @@ -91,9 +81,7 @@ export function DashboardHome() { }) await fetchRepos() } catch (error) { - toast.error('Re-indexing failed', { - description: 'Please check the console for details' - }) + toast.error('Re-indexing failed', { description: 'Please check the console for details' }) } finally { setLoading(false) } @@ -102,151 +90,154 @@ export function DashboardHome() { const selectedRepoData = repos.find(r => r.id === selectedRepo) const isRepoView = selectedRepo && selectedRepoData - // Tab button component - const TabButton = ({ tab, label }: { tab: RepoTab; label: string }) => ( - - ) + const tabs = [ + { id: 'overview', label: 'Overview', icon: '📊' }, + { id: 'search', label: 'Search', icon: '🔍' }, + { id: 'dependencies', label: 'Dependencies', icon: '🔗' }, + { id: 'insights', label: 'Code Style', icon: '✨' }, + { id: 'impact', label: 'Impact', icon: '💥' }, + ] as const return (
- {/* Performance Dashboard Toggle */} - {showPerformance && ( -
- -
- )} - - {/* Repository List View */} - {!isRepoView && ( -
-
-
-

Repositories

-

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

-
-
- + + {/* Repository List View */} + {!isRepoView && ( + + {/* Header */} +
+
+

Repositories

+

+ Semantic code search powered by AI +

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

{selectedRepoData.name}

-

{selectedRepoData.git_url}

+
+
+
+ + + +
+
+

{selectedRepoData.name}

+ + {selectedRepoData.git_url} + +
+
- - {selectedRepoData.status === 'indexed' && ( - - ✓ Indexed - - )}
-
- - {/* Tabs */} -
- - - - - -
- - {/* Tab Content */} -
- {activeTab === 'overview' && ( - - )} - - {activeTab === 'search' && ( - - )} - - {activeTab === 'dependencies' && ( - - )} - {activeTab === 'insights' && ( - - )} + {/* Tabs */} +
+ {tabs.map(tab => ( + + ))} +
- {activeTab === 'impact' && ( - - )} -
-
- )} + {/* Tab Content */} +
+ {activeTab === 'overview' && ( + + )} + {activeTab === 'search' && ( + + )} + {activeTab === 'dependencies' && ( + + )} + {activeTab === 'insights' && ( + + )} + {activeTab === 'impact' && ( + + )} +
+
+ )} +
) } diff --git a/frontend/src/components/dashboard/DashboardStats.tsx b/frontend/src/components/dashboard/DashboardStats.tsx new file mode 100644 index 0000000..1babe0c --- /dev/null +++ b/frontend/src/components/dashboard/DashboardStats.tsx @@ -0,0 +1,127 @@ +import { useEffect, useState } from 'react' +import { motion } from 'framer-motion' +import type { Repository } from '../../types' + +interface DashboardStatsProps { + repos: Repository[] +} + +// Animated counter hook +const useAnimatedCounter = (end: number, duration: number = 1500) => { + const [count, setCount] = useState(0) + + useEffect(() => { + if (end === 0) { setCount(0); return } + + let startTime: number | null = null + const animate = (timestamp: number) => { + if (!startTime) startTime = timestamp + const progress = Math.min((timestamp - startTime) / duration, 1) + // Ease out cubic + const easeOut = 1 - Math.pow(1 - progress, 3) + setCount(Math.floor(easeOut * end)) + if (progress < 1) requestAnimationFrame(animate) + } + requestAnimationFrame(animate) + }, [end, duration]) + + return count +} + +const StatCard = ({ label, value, suffix, gradient, glowColor, icon, delay = 0 }: { + label: string + value: number + suffix?: string + gradient: string + glowColor: string + icon: string + delay?: number +}) => { + const animatedValue = useAnimatedCounter(value) + + return ( + + {/* Glow effect */} +
+ + {/* Icon */} +
+ {icon} +
+ +
+

{label}

+
+ + {animatedValue.toLocaleString()} + + {suffix && ( + {suffix} + )} +
+
+ + ) +} + +export function DashboardStats({ repos }: DashboardStatsProps) { + const totalRepos = repos.length + const indexedRepos = repos.filter(r => r.status === 'indexed').length + const totalFunctions = repos.reduce((acc, r) => acc + (r.file_count || 0), 0) + const indexingRepos = repos.filter(r => r.status === 'indexing' || r.status === 'cloning').length + + return ( +
+ + + +
+
+ +
+

Indexed

+
+ + {indexedRepos} + + /{totalRepos} +
+ + {/* Indexing indicator */} + {indexingRepos > 0 && ( +
+ + {indexingRepos} indexing... +
+ )} +
+ + + +
+ ) +} From fc52660d5e6b6839801b73074e4f765608b8248f Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 14:45:46 -0500 Subject: [PATCH 2/8] fix(dashboard): unified blue-violet brand colors, cleaner design --- frontend/src/components/RepoList.tsx | 176 +++++++---------- frontend/src/components/RepoOverview.tsx | 177 +++++++----------- .../components/dashboard/DashboardStats.tsx | 128 ++++--------- 3 files changed, 175 insertions(+), 306 deletions(-) diff --git a/frontend/src/components/RepoList.tsx b/frontend/src/components/RepoList.tsx index d8a5a0a..e20fbc0 100644 --- a/frontend/src/components/RepoList.tsx +++ b/frontend/src/components/RepoList.tsx @@ -11,23 +11,24 @@ interface RepoListProps { } const StatusBadge = ({ status }: { status: string }) => { - const config = { - indexed: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', border: 'border-emerald-500/20', label: 'Indexed', dot: 'bg-emerald-400' }, - cloned: { bg: 'bg-blue-500/10', text: 'text-blue-400', border: 'border-blue-500/20', label: 'Pending Index', dot: 'bg-blue-400' }, - indexing: { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/20', label: 'Indexing...', dot: 'bg-amber-400 animate-pulse' }, - cloning: { bg: 'bg-amber-500/10', text: 'text-amber-400', border: 'border-amber-500/20', label: 'Cloning...', dot: 'bg-amber-400 animate-pulse' }, - error: { bg: 'bg-red-500/10', text: 'text-red-400', border: 'border-red-500/20', label: 'Error', dot: 'bg-red-400' }, - }[status] || { bg: 'bg-white/5', text: 'text-gray-400', border: 'border-white/10', label: status, dot: 'bg-gray-400' } - + const isIndexed = status === 'indexed' + const isPending = status === 'cloned' || status === 'cloning' || status === 'indexing' + return ( - - - {config.label} + + + {isIndexed ? 'Indexed' : isPending ? 'Pending' : 'Error'} ) } -// Featured card - larger, more info const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: { repo: Repository totalFunctions: number @@ -49,44 +50,34 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: { ref={cardRef} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} - transition={{ duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] }} - whileHover={{ y: -4, transition: { duration: 0.2 } }} + transition={{ duration: 0.4 }} + whileHover={{ y: -2 }} onClick={onSelect} onMouseMove={handleMouseMove} onMouseEnter={() => setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} - className="group relative text-left rounded-2xl overflow-hidden h-full min-h-[280px] - bg-gradient-to-br from-[#0f1114] via-[#0d0f12] to-[#0a0c0f] - border border-white/[0.08] hover:border-blue-500/30 - hover:shadow-2xl hover:shadow-blue-500/10 - focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-7" + className="group relative text-left rounded-2xl overflow-hidden w-full h-full + bg-[#111113] border border-white/[0.06] hover:border-blue-500/30 + focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-6" > - {/* Mouse-following glow */} + {/* Mouse glow */} {isHovering && (
)} - {/* Top accent line */} -
+ {/* Top gradient line */} +
- {/* Content */}
- {/* Badge */} -
- - ★ Most Functions - -
- {/* Header */}
-
- +
+
@@ -94,50 +85,39 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: {
{/* Title */} -
-

- {repo.name} -

-

{repo.branch}

-
+

+ {repo.name} +

+

{repo.branch}

{/* Stats */} -
-
- Functions indexed - +
+
+ Functions indexed + {(repo.file_count || 0).toLocaleString()}
- {/* Progress bar */} - {totalFunctions > 0 && ( + {totalFunctions > 0 && repo.file_count > 0 && (
-
+
-

{percentage}% of total indexed functions

+

{percentage}% of total

)}
- - {/* Hover CTA */} -
- - Explore - -
) } -// Regular card - compact const RepoCard = ({ repo, index, onSelect }: { repo: Repository index: number @@ -146,7 +126,6 @@ const RepoCard = ({ repo, index, onSelect }: { const cardRef = useRef(null) const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) const [isHovering, setIsHovering] = useState(false) - const isPending = repo.status === 'cloned' || repo.status === 'cloning' const handleMouseMove = (e: React.MouseEvent) => { if (!cardRef.current) return @@ -159,37 +138,31 @@ const RepoCard = ({ repo, index, onSelect }: { ref={cardRef} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} - transition={{ delay: index * 0.08, duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] }} - whileHover={{ y: -4, transition: { duration: 0.2 } }} + transition={{ delay: index * 0.1, duration: 0.4 }} + whileHover={{ y: -2 }} onClick={onSelect} onMouseMove={handleMouseMove} onMouseEnter={() => setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} - className={`group relative text-left rounded-2xl overflow-hidden h-full min-h-[140px] - border transition-all duration-300 - focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-5 - ${isPending - ? 'bg-gradient-to-br from-[#0f1114] to-[#0d0f12] border-dashed border-white/[0.08] hover:border-blue-500/30' - : 'bg-gradient-to-br from-[#0f1114] to-[#0d0f12] border-white/[0.08] hover:border-white/[0.15]' - } - hover:shadow-xl hover:shadow-blue-500/[0.06]`} + className="group relative text-left rounded-2xl overflow-hidden w-full + bg-[#111113] border border-white/[0.06] hover:border-white/[0.12] + focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-5" > - {/* Mouse-following glow */} + {/* Mouse glow */} {isHovering && (
)} - {/* Content */} -
+
{/* Header */} -
-
- +
+
+
@@ -197,18 +170,16 @@ const RepoCard = ({ repo, index, onSelect }: {
{/* Title */} -
-

- {repo.name} -

-

{repo.branch}

-
+

+ {repo.name} +

+

{repo.branch}

{/* Stats */} -
+
- Functions - + Functions + {(repo.file_count || 0).toLocaleString()}
@@ -219,35 +190,31 @@ const RepoCard = ({ repo, index, onSelect }: { } export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListProps) { - if (loading) { - return - } + if (loading) return if (repos.length === 0) { return ( -
- 📦 +
+ 📦

No repositories yet

-

- Add your first repository to start searching code semantically with AI +

+ Add your first repository to start searching code with AI

) } - // Sort repos: indexed first, then by file_count descending + // Sort: indexed first, then by function count const sortedRepos = useMemo(() => { return [...repos].sort((a, b) => { - // Indexed repos first if (a.status === 'indexed' && b.status !== 'indexed') return -1 if (b.status === 'indexed' && a.status !== 'indexed') return 1 - // Then by file count return (b.file_count || 0) - (a.file_count || 0) }) }, [repos]) @@ -255,18 +222,11 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro const totalFunctions = repos.reduce((acc, r) => acc + (r.file_count || 0), 0) const [featured, ...rest] = sortedRepos - // Determine grid layout based on repo count - const gridClass = rest.length === 0 - ? 'grid-cols-1' - : rest.length === 1 - ? 'grid-cols-1 lg:grid-cols-2' - : 'grid-cols-1 lg:grid-cols-3' - return ( -
- {/* Featured repo - spans full width on mobile, left column on desktop */} +
+ {/* Featured - spans 2 rows */} {featured && ( -
0 ? 'lg:row-span-2' : ''}> +
)} - {/* Rest of repos */} + {/* Rest */} {rest.map((repo, index) => ( ( - -
-
-
- {icon} - {label} -
-
{value}
-
- -) - export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewProps) { const [indexing, setIndexing] = useState(false) const [progress, setProgress] = useState(null) @@ -105,37 +81,51 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr } } - const statusConfig = { - indexed: { label: 'Indexed', color: 'text-emerald-400', bg: 'from-emerald-500/10 to-green-500/10', icon: '✓' }, - cloned: { label: 'Ready', color: 'text-blue-400', bg: 'from-blue-500/10 to-cyan-500/10', icon: '✓' }, - indexing: { label: 'Indexing', color: 'text-amber-400', bg: 'from-amber-500/10 to-orange-500/10', icon: '⏳' }, - }[repo.status] || { label: repo.status, color: 'text-gray-400', bg: 'from-gray-500/10 to-gray-500/10', icon: '•' } - return (
- {/* Bento Stats Grid */} + {/* Stats Grid */}
- - - + {/* Status */} + +

Status

+
+ + {repo.status === 'indexed' ? 'Indexed' : 'Pending'} +
+
+ + {/* Functions */} + +

Functions Indexed

+

+ {(repo.file_count || 0).toLocaleString()} +

+
+ + {/* Branch */} + +

Branch

+

{repo.branch}

+
{/* Indexing Progress */} @@ -143,17 +133,17 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr
-

Indexing in Progress

+ Indexing in Progress
{progress.progress_pct}%
- -
+ +
Files: {progress.files_processed}/{progress.total_files || '?'} Functions: {progress.functions_indexed}
@@ -162,90 +152,57 @@ export function RepoOverview({ repo, onReindex, apiUrl, apiKey }: RepoOverviewPr {/* Repository Details */} -

- 📋 Repository Details -

-
-
- Name - {repo.name} +

Repository Details

+
+
+ Name + {repo.name}
-
- Git URL - + -
- Local Path - {repo.local_path} +
+ Local Path + {repo.local_path}
{/* Actions */} -

- Actions -

-

- Re-indexing uses incremental mode - only processes changed files for 100x faster updates! +

Actions

+

+ Re-indexing uses incremental mode — only processes changed files.

- - {/* Quick Guide */} - -

- 💡 Quick Guide -

-
- {[ - { icon: '🔍', title: 'Search', desc: 'Find code by meaning, not keywords' }, - { icon: '🔗', title: 'Dependencies', desc: 'Visualize code architecture' }, - { icon: '✨', title: 'Code Style', desc: 'Analyze team coding patterns' }, - { icon: '💥', title: 'Impact', desc: 'See what breaks when you change a file' }, - ].map(item => ( -
- {item.icon} -
- {item.title} - {item.desc} -
-
- ))} -
-
) } diff --git a/frontend/src/components/dashboard/DashboardStats.tsx b/frontend/src/components/dashboard/DashboardStats.tsx index 1babe0c..cd167eb 100644 --- a/frontend/src/components/dashboard/DashboardStats.tsx +++ b/frontend/src/components/dashboard/DashboardStats.tsx @@ -6,18 +6,15 @@ interface DashboardStatsProps { repos: Repository[] } -// Animated counter hook -const useAnimatedCounter = (end: number, duration: number = 1500) => { +const useAnimatedCounter = (end: number, duration: number = 1200) => { const [count, setCount] = useState(0) useEffect(() => { if (end === 0) { setCount(0); return } - let startTime: number | null = null const animate = (timestamp: number) => { if (!startTime) startTime = timestamp const progress = Math.min((timestamp - startTime) / duration, 1) - // Ease out cubic const easeOut = 1 - Math.pow(1 - progress, 3) setCount(Math.floor(easeOut * end)) if (progress < 1) requestAnimationFrame(animate) @@ -28,100 +25,55 @@ const useAnimatedCounter = (end: number, duration: number = 1500) => { return count } -const StatCard = ({ label, value, suffix, gradient, glowColor, icon, delay = 0 }: { - label: string - value: number - suffix?: string - gradient: string - glowColor: string - icon: string - delay?: number -}) => { - const animatedValue = useAnimatedCounter(value) - - return ( - - {/* Glow effect */} -
- - {/* Icon */} -
- {icon} -
- -
-

{label}

-
- - {animatedValue.toLocaleString()} - - {suffix && ( - {suffix} - )} -
-
- - ) -} - export function DashboardStats({ repos }: DashboardStatsProps) { const totalRepos = repos.length const indexedRepos = repos.filter(r => r.status === 'indexed').length const totalFunctions = repos.reduce((acc, r) => acc + (r.file_count || 0), 0) const indexingRepos = repos.filter(r => r.status === 'indexing' || r.status === 'cloning').length + const animatedTotal = useAnimatedCounter(totalRepos) + const animatedIndexed = useAnimatedCounter(indexedRepos) + const animatedFunctions = useAnimatedCounter(totalFunctions) + + const stats = [ + { label: 'Total Repositories', value: animatedTotal, suffix: '' }, + { label: 'Indexed', value: animatedIndexed, suffix: `/${totalRepos}`, hasIndicator: indexingRepos > 0, indicatorText: `${indexingRepos} indexing...` }, + { label: 'Functions Indexed', value: animatedFunctions, suffix: '' }, + ] + return (
- - - -
-
- -
-

Indexed

-
- - {indexedRepos} - - /{totalRepos} -
+ {stats.map((stat, index) => ( + + {/* Subtle glow - same blue-violet for all */} +
- {/* Indexing indicator */} - {indexingRepos > 0 && ( -
- - {indexingRepos} indexing... +
+

{stat.label}

+
+ + {stat.value.toLocaleString()} + + {stat.suffix && ( + {stat.suffix} + )}
- )} -
- - - + + {stat.hasIndicator && ( +
+ + {stat.indicatorText} +
+ )} +
+ + ))}
) } From 28cf75b1531e00fb3b2886431aca0d11e3965aaf Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 14:47:40 -0500 Subject: [PATCH 3/8] fix(dashboard): single brand color - electric blue only - Removed ALL gradient colors from stats and repo cards - Single accent: blue-500 (#2563EB) everywhere - Clean, minimal design with proper contrast - Featured card has solid blue accent line - Consistent hover states with blue glow - No more rainbow effect --- frontend/src/components/RepoList.tsx | 151 +++++++++--------- .../components/dashboard/DashboardStats.tsx | 77 ++++----- 2 files changed, 116 insertions(+), 112 deletions(-) diff --git a/frontend/src/components/RepoList.tsx b/frontend/src/components/RepoList.tsx index e20fbc0..b3a031d 100644 --- a/frontend/src/components/RepoList.tsx +++ b/frontend/src/components/RepoList.tsx @@ -12,72 +12,71 @@ interface RepoListProps { const StatusBadge = ({ status }: { status: string }) => { const isIndexed = status === 'indexed' - const isPending = status === 'cloned' || status === 'cloning' || status === 'indexing' + + if (isIndexed) { + return ( + + + Indexed + + ) + } return ( - - - {isIndexed ? 'Indexed' : isPending ? 'Pending' : 'Error'} + + + Pending ) } +// Featured card - tall, prominent const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: { repo: Repository totalFunctions: number onSelect: () => void }) => { const cardRef = useRef(null) - const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) - const [isHovering, setIsHovering] = useState(false) - const percentage = totalFunctions > 0 ? Math.round((repo.file_count || 0) / totalFunctions * 100) : 0 - - const handleMouseMove = (e: React.MouseEvent) => { - if (!cardRef.current) return - const rect = cardRef.current.getBoundingClientRect() - setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }) - } + const [mousePos, setMousePos] = useState({ x: 0, y: 0 }) + const [hovering, setHovering] = useState(false) + const pct = totalFunctions > 0 ? Math.round((repo.file_count || 0) / totalFunctions * 100) : 0 return ( setIsHovering(true)} - onMouseLeave={() => setIsHovering(false)} - className="group relative text-left rounded-2xl overflow-hidden w-full h-full - bg-[#111113] border border-white/[0.06] hover:border-blue-500/30 - focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-6" + onMouseMove={(e) => { + if (!cardRef.current) return + const rect = cardRef.current.getBoundingClientRect() + setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top }) + }} + onMouseEnter={() => setHovering(true)} + onMouseLeave={() => setHovering(false)} + className="group relative text-left rounded-2xl overflow-hidden w-full h-full min-h-[300px] + bg-[#111113] border border-white/[0.06] hover:border-blue-500/40 + focus:outline-none focus:ring-2 focus:ring-blue-500/50 p-6 transition-colors" > - {/* Mouse glow */} - {isHovering && ( + {/* Mouse glow - BLUE only */} + {hovering && (
)} - {/* Top gradient line */} -
+ {/* Top accent - solid blue */} +
{/* Header */}
-
- +
+
@@ -85,7 +84,7 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: {
{/* Title */} -

+

{repo.name}

{repo.branch}

@@ -94,22 +93,23 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: {
Functions indexed - + {(repo.file_count || 0).toLocaleString()}
+ {/* Progress bar */} {totalFunctions > 0 && repo.file_count > 0 && (
-
+
-

{percentage}% of total

+

{pct}% of total indexed

)}
@@ -118,51 +118,50 @@ const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: { ) } +// Regular card - compact const RepoCard = ({ repo, index, onSelect }: { repo: Repository index: number onSelect: () => void }) => { const cardRef = useRef(null) - const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) - const [isHovering, setIsHovering] = useState(false) - - const handleMouseMove = (e: React.MouseEvent) => { - if (!cardRef.current) return - const rect = cardRef.current.getBoundingClientRect() - setMousePosition({ x: e.clientX - rect.left, y: e.clientY - rect.top }) - } + const [mousePos, setMousePos] = useState({ x: 0, y: 0 }) + const [hovering, setHovering] = useState(false) return ( setIsHovering(true)} - onMouseLeave={() => setIsHovering(false)} - className="group relative text-left rounded-2xl overflow-hidden w-full - bg-[#111113] border border-white/[0.06] hover:border-white/[0.12] - focus:outline-none focus:ring-2 focus:ring-blue-500/40 p-5" + onMouseMove={(e) => { + if (!cardRef.current) return + const rect = cardRef.current.getBoundingClientRect() + setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top }) + }} + onMouseEnter={() => setHovering(true)} + onMouseLeave={() => setHovering(false)} + className="group relative text-left rounded-2xl overflow-hidden w-full h-full + bg-[#111113] border border-white/[0.06] hover:border-white/[0.15] + focus:outline-none focus:ring-2 focus:ring-blue-500/50 p-5 transition-colors" > {/* Mouse glow */} - {isHovering && ( + {hovering && (
)} -
+
{/* Header */}
-
- +
+
@@ -173,13 +172,13 @@ const RepoCard = ({ repo, index, onSelect }: {

{repo.name}

-

{repo.branch}

+

{repo.branch}

{/* Stats */} -
+
Functions - + {(repo.file_count || 0).toLocaleString()}
@@ -199,8 +198,10 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro animate={{ opacity: 1 }} className="bg-[#111113] border border-white/[0.06] rounded-2xl p-16 text-center" > -
- 📦 +
+ + +

No repositories yet

@@ -210,7 +211,7 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro ) } - // Sort: indexed first, then by function count + // Sort: indexed first, then by function count desc const sortedRepos = useMemo(() => { return [...repos].sort((a, b) => { if (a.status === 'indexed' && b.status !== 'indexed') return -1 @@ -223,10 +224,10 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro const [featured, ...rest] = sortedRepos return ( -

- {/* Featured - spans 2 rows */} +
+ {/* Featured - spans 2 rows on desktop */} {featured && ( -
+
)} - {/* Rest */} + {/* Other repos */} {rest.map((repo, index) => ( r.status === 'indexed').length const totalFunctions = repos.reduce((acc, r) => acc + (r.file_count || 0), 0) - const indexingRepos = repos.filter(r => r.status === 'indexing' || r.status === 'cloning').length + const indexingCount = repos.filter(r => r.status === 'indexing' || r.status === 'cloning').length const animatedTotal = useAnimatedCounter(totalRepos) const animatedIndexed = useAnimatedCounter(indexedRepos) const animatedFunctions = useAnimatedCounter(totalFunctions) - const stats = [ - { label: 'Total Repositories', value: animatedTotal, suffix: '' }, - { label: 'Indexed', value: animatedIndexed, suffix: `/${totalRepos}`, hasIndicator: indexingRepos > 0, indicatorText: `${indexingRepos} indexing...` }, - { label: 'Functions Indexed', value: animatedFunctions, suffix: '' }, - ] - return (
- {stats.map((stat, index) => ( - - {/* Subtle glow - same blue-violet for all */} -
- -
-

{stat.label}

-
- - {stat.value.toLocaleString()} - - {stat.suffix && ( - {stat.suffix} - )} -
- - {stat.hasIndicator && ( -
- - {stat.indicatorText} -
- )} + {/* Total Repos */} + +

Total Repositories

+

{animatedTotal}

+
+ + {/* Indexed */} + +

Indexed

+
+ {animatedIndexed} + /{totalRepos} +
+ {indexingCount > 0 && ( +
+ + {indexingCount} indexing...
-
- ))} + )} + + + {/* Functions */} + +

Functions Indexed

+

{animatedFunctions.toLocaleString()}

+
) } From 027f0ad56c80920f7626bbebc141c53ccceb6646 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 14:57:12 -0500 Subject: [PATCH 4/8] feat(dashboard): equal grid layout - clean and scalable - Removed bento/featured card logic - All repos same size in 3-column grid - Sorted: indexed first, then by function count - Cleaner code, 143 lines vs 250 - Scales better for 10+ repos --- frontend/src/components/RepoList.tsx | 152 ++++----------------------- 1 file changed, 22 insertions(+), 130 deletions(-) diff --git a/frontend/src/components/RepoList.tsx b/frontend/src/components/RepoList.tsx index b3a031d..14b241a 100644 --- a/frontend/src/components/RepoList.tsx +++ b/frontend/src/components/RepoList.tsx @@ -13,112 +13,19 @@ interface RepoListProps { const StatusBadge = ({ status }: { status: string }) => { const isIndexed = status === 'indexed' - if (isIndexed) { - return ( - - - Indexed - - ) - } - - return ( - - - Pending - - ) -} - -// Featured card - tall, prominent -const FeaturedRepoCard = ({ repo, totalFunctions, onSelect }: { - repo: Repository - totalFunctions: number - onSelect: () => void -}) => { - const cardRef = useRef(null) - const [mousePos, setMousePos] = useState({ x: 0, y: 0 }) - const [hovering, setHovering] = useState(false) - const pct = totalFunctions > 0 ? Math.round((repo.file_count || 0) / totalFunctions * 100) : 0 - return ( - { - if (!cardRef.current) return - const rect = cardRef.current.getBoundingClientRect() - setMousePos({ x: e.clientX - rect.left, y: e.clientY - rect.top }) - }} - onMouseEnter={() => setHovering(true)} - onMouseLeave={() => setHovering(false)} - className="group relative text-left rounded-2xl overflow-hidden w-full h-full min-h-[300px] - bg-[#111113] border border-white/[0.06] hover:border-blue-500/40 - focus:outline-none focus:ring-2 focus:ring-blue-500/50 p-6 transition-colors" + - {/* Mouse glow - BLUE only */} - {hovering && ( -
- )} - - {/* Top accent - solid blue */} -
- -
- {/* Header */} -
-
- - - -
- -
- - {/* Title */} -

- {repo.name} -

-

{repo.branch}

- - {/* Stats */} -
-
- Functions indexed - - {(repo.file_count || 0).toLocaleString()} - -
- - {/* Progress bar */} - {totalFunctions > 0 && repo.file_count > 0 && ( -
-
- -
-

{pct}% of total indexed

-
- )} -
-
- + + {isIndexed ? 'Indexed' : 'Pending'} + ) } -// Regular card - compact const RepoCard = ({ repo, index, onSelect }: { repo: Repository index: number @@ -133,7 +40,7 @@ const RepoCard = ({ repo, index, onSelect }: { ref={cardRef} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} - transition={{ delay: index * 0.1 }} + transition={{ delay: index * 0.05, duration: 0.3 }} whileHover={{ y: -3 }} onClick={onSelect} onMouseMove={(e) => { @@ -143,8 +50,8 @@ const RepoCard = ({ repo, index, onSelect }: { }} onMouseEnter={() => setHovering(true)} onMouseLeave={() => setHovering(false)} - className="group relative text-left rounded-2xl overflow-hidden w-full h-full - bg-[#111113] border border-white/[0.06] hover:border-white/[0.15] + className="group relative text-left rounded-2xl overflow-hidden w-full + bg-[#111113] border border-white/[0.06] hover:border-blue-500/40 focus:outline-none focus:ring-2 focus:ring-blue-500/50 p-5 transition-colors" > {/* Mouse glow */} @@ -157,11 +64,11 @@ const RepoCard = ({ repo, index, onSelect }: { /> )} -
+
{/* Header */}
-
- +
+
@@ -169,16 +76,16 @@ const RepoCard = ({ repo, index, onSelect }: {
{/* Title */} -

+

{repo.name}

-

{repo.branch}

+

{repo.branch}

{/* Stats */} -
+
- Functions - + Functions + {(repo.file_count || 0).toLocaleString()}
@@ -220,28 +127,13 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro }) }, [repos]) - const totalFunctions = repos.reduce((acc, r) => acc + (r.file_count || 0), 0) - const [featured, ...rest] = sortedRepos - return ( -
- {/* Featured - spans 2 rows on desktop */} - {featured && ( -
- onSelect(featured.id)} - /> -
- )} - - {/* Other repos */} - {rest.map((repo, index) => ( +
+ {sortedRepos.map((repo, index) => ( onSelect(repo.id)} /> ))} From f6e69c5fb20801f48de301af0b6d565768617dd4 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 16:29:54 -0500 Subject: [PATCH 5/8] fix(brand): unified blue color system across all dashboard tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StyleInsights: green/purple stats → blue-500 - StyleInsights: green percentage badges → blue variant - DependencyGraph: mixed white/green/blue stats → all blue-500 - ImpactAnalyzer: yellow/green stats → blue-500 - Preserved semantic colors for risk indicators (red/yellow/green) Brand rule: ALL stat numbers use blue-500, semantic colors only for risk --- frontend/src/components/DependencyGraph.tsx | 8 ++++---- frontend/src/components/ImpactAnalyzer.tsx | 6 +++--- frontend/src/components/StyleInsights.tsx | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/DependencyGraph.tsx b/frontend/src/components/DependencyGraph.tsx index 193b2a0..e41d2a0 100644 --- a/frontend/src/components/DependencyGraph.tsx +++ b/frontend/src/components/DependencyGraph.tsx @@ -235,21 +235,21 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps
Total Files
-
{allNodes.length}
+
{allNodes.length}
Dependencies
-
{edges.length}
+
{edges.length}
Avg per File
-
+
{metrics?.avg_dependencies?.toFixed(1) || 0}
Showing
-
{nodes.length}
+
{nodes.length}
diff --git a/frontend/src/components/ImpactAnalyzer.tsx b/frontend/src/components/ImpactAnalyzer.tsx index 6130e6a..a656d0e 100644 --- a/frontend/src/components/ImpactAnalyzer.tsx +++ b/frontend/src/components/ImpactAnalyzer.tsx @@ -129,7 +129,7 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)
Direct Dependencies
-
+
{result.dependency_count}
@@ -139,7 +139,7 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)
Total Impact
-
+
{result.dependent_count}
@@ -149,7 +149,7 @@ export function ImpactAnalyzer({ repoId, apiUrl, apiKey }: ImpactAnalyzerProps)
Test Files
-
+
{result.test_files?.length || 0}
diff --git a/frontend/src/components/StyleInsights.tsx b/frontend/src/components/StyleInsights.tsx index 81ca84f..7b5abd2 100644 --- a/frontend/src/components/StyleInsights.tsx +++ b/frontend/src/components/StyleInsights.tsx @@ -41,14 +41,14 @@ export function StyleInsights({ repoId, apiUrl, apiKey }: StyleInsightsProps) {
Async Adoption
-
+
{data.summary?.async_adoption || '0%'}
Type Hints
-
+
{data.summary?.type_hints_usage || '0%'}
@@ -126,7 +126,7 @@ export function StyleInsights({ repoId, apiUrl, apiKey }: StyleInsightsProps) { Async/Await Usage
{data.patterns?.async_usage} - + {data.patterns?.async_percentage?.toFixed(0)}%
@@ -136,7 +136,7 @@ export function StyleInsights({ repoId, apiUrl, apiKey }: StyleInsightsProps) { Type Annotations
{data.patterns?.type_annotations} - + {data.patterns?.typed_percentage?.toFixed(0)}%
From e5c6860b878f9a0fc80216b512370b2a46c590ca Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 16:39:31 -0500 Subject: [PATCH 6/8] fix(ui): replace unprofessional emojis with Lucide icons - Removed childish emoji icons from dashboard tabs - Added proper Lucide React icons: - Overview: LayoutDashboard - Search: Search - Dependencies: GitFork - Code Style: Code2 - Impact: Zap - Icons now match brand blue when active - Consistent 16px sizing (w-4 h-4) Professional icon system like Linear/Vercel --- frontend/src/components/dashboard/DashboardHome.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/dashboard/DashboardHome.tsx b/frontend/src/components/dashboard/DashboardHome.tsx index 8f0a91d..feeb9c8 100644 --- a/frontend/src/components/dashboard/DashboardHome.tsx +++ b/frontend/src/components/dashboard/DashboardHome.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { toast } from 'sonner' +import { LayoutDashboard, Search, GitFork, Code2, Zap } from 'lucide-react' import { useAuth } from '../../contexts/AuthContext' import { RepoList } from '../RepoList' import { AddRepoForm } from '../AddRepoForm' @@ -91,11 +92,11 @@ export function DashboardHome() { const isRepoView = selectedRepo && selectedRepoData const tabs = [ - { id: 'overview', label: 'Overview', icon: '📊' }, - { id: 'search', label: 'Search', icon: '🔍' }, - { id: 'dependencies', label: 'Dependencies', icon: '🔗' }, - { id: 'insights', label: 'Code Style', icon: '✨' }, - { id: 'impact', label: 'Impact', icon: '💥' }, + { id: 'overview', label: 'Overview', icon: LayoutDashboard }, + { id: 'search', label: 'Search', icon: Search }, + { id: 'dependencies', label: 'Dependencies', icon: GitFork }, + { id: 'insights', label: 'Code Style', icon: Code2 }, + { id: 'impact', label: 'Impact', icon: Zap }, ] as const return ( @@ -189,7 +190,7 @@ export function DashboardHome() { : 'text-gray-400 hover:text-white hover:bg-white/5' }`} > - {tab.icon} + {tab.label} ))} From efa716a1f06b10a6f0087d0449280ce17b0f5eb0 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 16:45:25 -0500 Subject: [PATCH 7/8] fix(ui): replace ALL emojis with Lucide icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AddRepoForm.tsx: - 📦 → Package icon (modal header) - 🚀 → Plus icon (submit button) - ✕ → X icon (close button) SearchPanel.tsx: - ⚡ → Zap icon (cached badge) - 🔍 → Search icon (empty state) DependencyGraph.tsx: - 💡 → Lightbulb icon (hint text) Zero emojis remaining in main UI components. Professional icon system complete. --- frontend/src/components/AddRepoForm.tsx | 7 ++++--- frontend/src/components/DependencyGraph.tsx | 6 ++++-- frontend/src/components/SearchPanel.tsx | 8 ++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/AddRepoForm.tsx b/frontend/src/components/AddRepoForm.tsx index 5e44ddd..7f01544 100644 --- a/frontend/src/components/AddRepoForm.tsx +++ b/frontend/src/components/AddRepoForm.tsx @@ -1,5 +1,6 @@ import { useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' +import { Package, Plus, X } from 'lucide-react' interface AddRepoFormProps { onAdd: (gitUrl: string, branch: string) => Promise @@ -53,7 +54,7 @@ export function AddRepoForm({ onAdd, loading }: AddRepoFormProps) {
- 📦 +

Add Repository

@@ -65,7 +66,7 @@ export function AddRepoForm({ onAdd, loading }: AddRepoFormProps) { className="w-8 h-8 flex items-center justify-center rounded-lg text-gray-400 hover:text-white hover:bg-white/10 transition-colors" disabled={loading} > - ✕ +
@@ -123,7 +124,7 @@ export function AddRepoForm({ onAdd, loading }: AddRepoFormProps) { ) : ( <> - 🚀 + Add & Index )} diff --git a/frontend/src/components/DependencyGraph.tsx b/frontend/src/components/DependencyGraph.tsx index e41d2a0..f788134 100644 --- a/frontend/src/components/DependencyGraph.tsx +++ b/frontend/src/components/DependencyGraph.tsx @@ -9,6 +9,7 @@ import ReactFlow, { } from 'reactflow' import type { Node, Edge } from 'reactflow' import dagre from 'dagre' +import { Lightbulb } from 'lucide-react' import 'reactflow/dist/style.css' import { useDependencyGraph } from '../hooks/useCachedQuery' @@ -357,8 +358,9 @@ export function DependencyGraph({ repoId, apiUrl, apiKey }: DependencyGraphProps Dependency
-
- 💡 Click any node to highlight its dependencies • Drag to pan • Scroll to zoom +
+ + Click any node to highlight its dependencies • Drag to pan • Scroll to zoom
diff --git a/frontend/src/components/SearchPanel.tsx b/frontend/src/components/SearchPanel.tsx index 4d0a2d7..ab91e0d 100644 --- a/frontend/src/components/SearchPanel.tsx +++ b/frontend/src/components/SearchPanel.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { toast } from 'sonner'; +import { Zap, Search } from 'lucide-react'; import { SearchBox, ResultCard } from './search'; import type { SearchResult } from '../types'; @@ -83,7 +84,10 @@ export function SearchPanel({ repoId, apiUrl, apiKey, repoUrl }: SearchPanelProp {cached && ( <> - ⚡ Cached + + + Cached + )}
@@ -108,7 +112,7 @@ export function SearchPanel({ repoId, apiUrl, apiKey, repoUrl }: SearchPanelProp {results.length === 0 && hasSearched && !loading && (
- 🔍 +

No results found

From 8b75e42890d0abba1e8a4acf479684ebfbeb1489 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Thu, 8 Jan 2026 16:56:11 -0500 Subject: [PATCH 8/8] fix(ui): replace AI summary emoji with Lucide Sparkles icon --- frontend/src/components/search/ResultCard.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/search/ResultCard.tsx b/frontend/src/components/search/ResultCard.tsx index 35079b5..81ec78a 100644 --- a/frontend/src/components/search/ResultCard.tsx +++ b/frontend/src/components/search/ResultCard.tsx @@ -2,6 +2,7 @@ import { useState, useRef, useEffect } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { toast } from 'sonner'; +import { Sparkles } from 'lucide-react'; import type { SearchResult } from '../../types'; interface ResultCardProps { @@ -121,7 +122,7 @@ export function ResultCard({ {aiSummary && isTopResult && (

- +

AI Summary

{aiSummary}