frontend changes

This commit is contained in:
Chandini 2025-09-30 15:47:58 +05:30
parent b6d91b9a08
commit 0831dd5658
4 changed files with 462 additions and 34 deletions

View File

@ -0,0 +1,320 @@
"use client"
import { useEffect, useState } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import {
ArrowLeft,
Brain,
Code,
FileText,
GitBranch,
Star,
Shield,
TrendingUp,
AlertTriangle,
CheckCircle,
Clock,
Zap,
Target,
BarChart3,
Layers,
Cpu,
Search,
File,
Folder,
Eye,
AlertCircle
} from "lucide-react"
interface FileAnalysis {
id: string
name: string
path: string
type: 'file' | 'folder'
status: 'scanning' | 'analyzing' | 'completed' | 'pending'
size?: string
language?: string
issues?: number
score?: number
details?: string
}
export default function AIAnalysisPage() {
const router = useRouter()
const searchParams = useSearchParams()
const repoId = searchParams.get('repoId')
const repoName = searchParams.get('repoName') || 'Repository'
const [analysisProgress, setAnalysisProgress] = useState(0)
const [isAnalyzing, setIsAnalyzing] = useState(true)
const [currentFile, setCurrentFile] = useState('Initializing analysis...')
const [files, setFiles] = useState<FileAnalysis[]>([
{ id: '1', name: 'package.json', path: '/package.json', type: 'file', status: 'pending', language: 'JSON', size: '2.1 KB' },
{ id: '2', name: 'src', path: '/src', type: 'folder', status: 'pending' },
{ id: '3', name: 'App.js', path: '/src/App.js', type: 'file', status: 'pending', language: 'JavaScript', size: '5.2 KB' },
{ id: '4', name: 'components', path: '/src/components', type: 'folder', status: 'pending' },
{ id: '5', name: 'Header.jsx', path: '/src/components/Header.jsx', type: 'file', status: 'pending', language: 'JavaScript', size: '3.8 KB' },
{ id: '6', name: 'Footer.jsx', path: '/src/components/Footer.jsx', type: 'file', status: 'pending', language: 'JavaScript', size: '2.5 KB' },
{ id: '7', name: 'utils', path: '/src/utils', type: 'folder', status: 'pending' },
{ id: '8', name: 'helpers.js', path: '/src/utils/helpers.js', type: 'file', status: 'pending', language: 'JavaScript', size: '4.1 KB' },
{ id: '9', name: 'README.md', path: '/README.md', type: 'file', status: 'pending', language: 'Markdown', size: '1.8 KB' },
{ id: '10', name: 'styles', path: '/styles', type: 'folder', status: 'pending' },
{ id: '11', name: 'main.css', path: '/styles/main.css', type: 'file', status: 'pending', language: 'CSS', size: '6.3 KB' },
{ id: '12', name: 'tests', path: '/tests', type: 'folder', status: 'pending' },
{ id: '13', name: 'App.test.js', path: '/tests/App.test.js', type: 'file', status: 'pending', language: 'JavaScript', size: '2.9 KB' }
])
useEffect(() => {
if (!repoId) {
router.push('/github/repos')
return
}
let fileIndex = 0
let progress = 0
const interval = setInterval(() => {
if (fileIndex < files.length) {
const currentFileData = files[fileIndex]
// Update current file being analyzed
setCurrentFile(`Analyzing ${currentFileData.name}...`)
// Update file status to scanning
setFiles(prev => prev.map((file, index) =>
index === fileIndex ? { ...file, status: 'scanning' as const } : file
))
// After a short delay, mark as analyzing
setTimeout(() => {
setFiles(prev => prev.map((file, index) =>
index === fileIndex ? { ...file, status: 'analyzing' as const } : file
))
}, 500)
// After another delay, mark as completed with mock data
setTimeout(() => {
setFiles(prev => prev.map((file, index) =>
index === fileIndex ? {
...file,
status: 'completed' as const,
score: Math.floor(Math.random() * 30) + 70, // 70-100
issues: Math.floor(Math.random() * 5),
details: file.type === 'file' ? 'Analysis completed successfully' : 'Directory scanned'
} : file
))
}, 1000)
progress = Math.min(100, ((fileIndex + 1) / files.length) * 100)
setAnalysisProgress(progress)
fileIndex++
} else {
// Complete analysis
setIsAnalyzing(false)
setCurrentFile('Analysis completed!')
setAnalysisProgress(100)
clearInterval(interval)
}
}, 1500)
return () => clearInterval(interval)
}, [repoId, router, files.length])
const getStatusIcon = (status: FileAnalysis['status'], type: FileAnalysis['type']) => {
switch (status) {
case 'completed':
return <CheckCircle className="h-4 w-4 text-green-400" />
case 'analyzing':
return <div className="h-4 w-4 border-2 border-blue-400 border-t-transparent rounded-full animate-spin" />
case 'scanning':
return <Search className="h-4 w-4 text-yellow-400 animate-pulse" />
case 'pending':
return <Clock className="h-4 w-4 text-white/40" />
}
}
const getFileIcon = (type: FileAnalysis['type'], language?: string) => {
if (type === 'folder') {
return <Folder className="h-4 w-4 text-blue-400" />
}
switch (language) {
case 'JavaScript':
return <Code className="h-4 w-4 text-yellow-400" />
case 'CSS':
return <FileText className="h-4 w-4 text-blue-400" />
case 'Markdown':
return <FileText className="h-4 w-4 text-gray-400" />
case 'JSON':
return <FileText className="h-4 w-4 text-orange-400" />
default:
return <File className="h-4 w-4 text-white/60" />
}
}
const getScoreColor = (score: number) => {
if (score >= 90) return 'text-green-400'
if (score >= 80) return 'text-yellow-400'
if (score >= 70) return 'text-orange-400'
return 'text-red-400'
}
return (
<div className="mx-auto max-w-7xl px-4 py-8 space-y-8">
{/* Header */}
<div className="flex items-center gap-4">
<Link href="/github/repos">
<Button variant="ghost" className="text-white/80 hover:text-white">
<ArrowLeft className="mr-2 h-4 w-4" /> Back to Repositories
</Button>
</Link>
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white flex items-center gap-3">
<Brain className="h-8 w-8 text-orange-400" />
AI Code Analysis
</h1>
<p className="text-white/60 mt-1">Analyzing: <span className="text-orange-400 font-medium">{repoName}</span></p>
</div>
</div>
{/* Analysis Progress */}
{isAnalyzing && (
<Card className="bg-white/5 border-white/10">
<CardContent className="p-6">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-white">Analysis Progress</h3>
<span className="text-sm text-white/60">{Math.round(analysisProgress)}%</span>
</div>
<Progress value={analysisProgress} className="h-2" />
<p className="text-white/80 text-sm flex items-center gap-2">
<Eye className="h-4 w-4 text-blue-400" />
{currentFile}
</p>
</div>
</CardContent>
</Card>
)}
{/* File Analysis List */}
<Card className="bg-white/5 border-white/10">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<FileText className="h-6 w-6 text-orange-400" />
File Analysis Results
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<div className="divide-y divide-white/10">
{files.map((file) => (
<div key={file.id} className="p-4 hover:bg-white/5 transition-colors">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 min-w-0 flex-1">
{getFileIcon(file.type, file.language)}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="font-medium text-white truncate">{file.name}</span>
{file.language && (
<Badge variant="outline" className="text-xs border-white/20 text-white/60">
{file.language}
</Badge>
)}
{file.size && (
<span className="text-xs text-white/40">{file.size}</span>
)}
</div>
<p className="text-sm text-white/60 truncate">{file.path}</p>
</div>
</div>
<div className="flex items-center gap-3">
{file.status === 'completed' && file.score && (
<div className="flex items-center gap-2">
{file.issues && file.issues > 0 && (
<div className="flex items-center gap-1 text-red-400">
<AlertCircle className="h-4 w-4" />
<span className="text-sm">{file.issues}</span>
</div>
)}
<Badge className={`${getScoreColor(file.score)} bg-transparent border`}>
{file.score}/100
</Badge>
</div>
)}
{getStatusIcon(file.status, file.type)}
</div>
</div>
{file.status === 'completed' && file.details && (
<div className="mt-2 text-sm text-white/70">
{file.details}
</div>
)}
</div>
))}
</div>
</CardContent>
</Card>
{/* Summary */}
{!isAnalyzing && (
<Card className="bg-white/5 border-white/10">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<BarChart3 className="h-6 w-6 text-orange-400" />
Analysis Summary
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<div className="text-center">
<div className="text-2xl font-bold text-green-400">
{files.filter(f => f.status === 'completed').length}
</div>
<div className="text-white/60 text-sm">Files Analyzed</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-yellow-400">
{files.filter(f => f.language).length}
</div>
<div className="text-white/60 text-sm">Languages Found</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-red-400">
{files.reduce((sum, f) => sum + (f.issues || 0), 0)}
</div>
<div className="text-white/60 text-sm">Issues Found</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-blue-400">
{Math.round(files.filter(f => f.score).reduce((sum, f) => sum + (f.score || 0), 0) / files.filter(f => f.score).length) || 0}
</div>
<div className="text-white/60 text-sm">Avg Score</div>
</div>
</div>
</CardContent>
</Card>
)}
{/* Action Buttons */}
{!isAnalyzing && (
<div className="flex justify-center gap-4">
<Button className="bg-orange-600 hover:bg-orange-700 text-white">
<Layers className="mr-2 h-4 w-4" />
Generate Detailed Report
</Button>
<Button variant="outline" className="border-white/20 text-white hover:bg-white/10">
<Cpu className="mr-2 h-4 w-4" />
Export Analysis
</Button>
</div>
)}
</div>
)
}

View File

@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { ArrowLeft, ExternalLink, FolderGit2, GitFork, Star, Shield, Search } from "lucide-react"
import { ArrowLeft, ExternalLink, FolderGit2, GitFork, Star, Shield, Search, Brain } from "lucide-react"
import { getGitHubAuthStatus, getUserRepositories, type GitHubRepoSummary } from "@/lib/api/github"
export default function GitHubUserReposPage() {
@ -16,6 +16,7 @@ export default function GitHubUserReposPage() {
const [authUrl, setAuthUrl] = useState<string | null>(null)
const [repos, setRepos] = useState<GitHubRepoSummary[]>([])
const [query, setQuery] = useState("")
const [analyzingRepo, setAnalyzingRepo] = useState<string | null>(null)
useEffect(() => {
let mounted = true
@ -63,6 +64,25 @@ export default function GitHubUserReposPage() {
window.location.href = url
}
const handleAnalyzeWithAI = async (repo: GitHubRepoSummary) => {
try {
setAnalyzingRepo(repo.full_name || repo.name || '')
// Navigate to AI analysis page with repository details
const repoId = (repo as any).id || repo.full_name || repo.name
const repoName = repo.full_name || repo.name
const analysisUrl = `/github/analyze?repoId=${encodeURIComponent(repoId)}&repoName=${encodeURIComponent(repoName)}`
window.location.href = analysisUrl
} catch (error) {
console.error('Error analyzing repository:', error)
alert('Failed to analyze repository. Please try again.')
} finally {
setAnalyzingRepo(null)
}
}
return (
<div className="mx-auto max-w-7xl px-4 py-8 space-y-8">
<div className="flex items-center gap-4">
@ -151,14 +171,24 @@ export default function GitHubUserReposPage() {
)}
</div>
</div>
<Link
href={`/github/repo?id=${encodeURIComponent(String((repo as any).id ?? ''))}`}
className="shrink-0"
>
<Button variant="outline" className="border-white/20 text-white hover:bg-white/10">
<ExternalLink className="mr-2 h-4 w-4" /> Open
<div className="flex items-center gap-2 shrink-0">
<Button
variant="outline"
className="border-white/20 text-white hover:bg-white/10"
onClick={() => handleAnalyzeWithAI(repo)}
disabled={analyzingRepo === (repo.full_name || repo.name)}
>
<Brain className="mr-2 h-4 w-4" />
{analyzingRepo === (repo.full_name || repo.name) ? 'Analyzing...' : 'Analyze with AI'}
</Button>
</Link>
<Link
href={`/github/repo?id=${encodeURIComponent(String((repo as any).id ?? ''))}`}
>
<Button variant="outline" className="border-white/20 text-white hover:bg-white/10">
<ExternalLink className="mr-2 h-4 w-4" /> Open
</Button>
</Link>
</div>
</div>
</div>
))}

View File

@ -1,12 +1,13 @@
"use client"
import { Suspense, useEffect } from "react"
import { useRouter } from "next/navigation"
import { useRouter, useSearchParams } from "next/navigation"
import { MainDashboard } from "@/components/main-dashboard"
import { useAuth } from "@/contexts/auth-context"
export default function ProjectBuilderPage() {
const router = useRouter()
const searchParams = useSearchParams()
const { user, isLoading } = useAuth()
useEffect(() => {
@ -17,6 +18,36 @@ export default function ProjectBuilderPage() {
}
}, [user, isLoading, router])
// Handle GitHub OAuth callback parameters
useEffect(() => {
if (isLoading || !user) return
const githubConnected = searchParams.get('github_connected')
const githubUser = searchParams.get('user')
const repoAttached = searchParams.get('repo_attached')
const repositoryId = searchParams.get('repository_id')
const syncStatus = searchParams.get('sync_status')
if (githubConnected === '1') {
console.log('🎉 GitHub OAuth callback successful!', {
githubUser,
repoAttached,
repositoryId,
syncStatus
})
// Show success message
if (repoAttached === '1') {
alert(`🎉 Repository attached successfully!\n\nGitHub User: ${githubUser}\nRepository ID: ${repositoryId}\nSync Status: ${syncStatus}`)
} else {
alert(`🎉 GitHub account connected successfully!\n\nGitHub User: ${githubUser}`)
}
// Clean up URL parameters
router.replace('/project-builder')
}
}, [isLoading, user, searchParams, router])
if (isLoading || !user) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>
}

View File

@ -96,21 +96,50 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
}))
} catch {}
await attachRepository({
const result = await attachRepository({
repository_url: gitUrl.trim(),
branch_name: (gitBranch?.trim() || undefined),
})
// If we reach here without 401, repo is public and attached. Nothing to auth.
// If we reach here without 401, repo is public and attached successfully
console.log('✅ Repository attached successfully:', result)
alert('Repository attached successfully! You can now proceed with your project.')
setShowCreateOptionDialog(false)
setShowGitForm(false)
setGitProvider('')
setGitUrl('')
setGitBranch('main')
} catch (err: any) {
const status = err?.response?.status
const data = err?.response?.data
if (status === 401 && (data?.requires_auth || data?.auth_url)) {
const url: string = data?.auth_url
let data = err?.response?.data
// Some proxies or middlewares may stringify JSON error bodies; handle that here
if (typeof data === 'string') {
try { data = JSON.parse(data) } catch {}
}
console.log('🔍 Attach repository response:', { status, data })
if (status === 401 && (data?.requires_auth || data?.auth_url || data?.service_auth_url)) {
const url: string = data?.service_auth_url || data?.auth_url
if (url) {
console.log('🔐 Redirecting to GitHub OAuth:', url)
window.location.replace(url)
return
}
}
if (status === 403) {
alert('Repository not accessible - you may not have permission to access this repository')
return
}
if (status === 404) {
alert('Repository not found - please check the URL and try again')
return
}
console.error('Error generating auth URL via attach:', err)
alert(data?.message || 'Failed to generate authentication URL. Please try again.')
} finally {
@ -493,28 +522,46 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
}
} catch (attachErr) {
console.error('[TemplateSelectionStep] attachRepository failed:', attachErr)
const err: any = attachErr
const status = err?.response?.status
const data = err?.response?.data
console.log('🔍 HandleCreateFromGit error response:', { status, data })
// If backend signals GitHub auth required, open the OAuth URL for this user
try {
const err: any = attachErr
const status = err?.response?.status
const data = err?.response?.data
if (status === 401 && (data?.requires_auth || data?.message?.includes('authentication'))) {
// Use the exact URL provided by backend (already includes redirect=1 and user_id when needed)
const url: string = data?.auth_url
if (!url) { alert('Authentication URL is missing.'); return }
// Persist pending repo so we resume after OAuth callback
try {
sessionStorage.setItem('pending_git_attach', JSON.stringify({
repository_url: gitUrl.trim(),
branch_name: (gitBranch?.trim() || undefined)
}))
} catch {}
// Force same-tab redirect directly to GitHub consent screen
window.location.replace(url)
return
if (status === 401 && (data?.requires_auth || data?.message?.includes('authentication'))) {
const url: string = data?.auth_url
if (!url) {
alert('Authentication URL is missing.');
return
}
} catch {}
alert('Failed to attach repository. Please verify the URL/branch and your auth.');
// Persist pending repo so we resume after OAuth callback
try {
sessionStorage.setItem('pending_git_attach', JSON.stringify({
repository_url: gitUrl.trim(),
branch_name: (gitBranch?.trim() || undefined)
}))
} catch {}
console.log('🔐 Redirecting to GitHub OAuth for repository attachment:', url)
// Force same-tab redirect directly to GitHub consent screen
window.location.replace(url)
return
}
if (status === 403) {
alert('Repository not accessible - you may not have permission to access this repository')
return
}
if (status === 404) {
alert('Repository not found - please check the URL and try again')
return
}
alert(data?.message || 'Failed to attach repository. Please verify the URL/branch and your auth.');
return;
}
} catch (error) {