frontend changes
This commit is contained in:
parent
b6d91b9a08
commit
0831dd5658
320
src/app/github/analyze/page.tsx
Normal file
320
src/app/github/analyze/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user