done the redirection issue
This commit is contained in:
parent
6698d11597
commit
64f2e401af
@ -19,16 +19,81 @@ function ProjectBuilderContent() {
|
||||
}
|
||||
}, [user, isLoading, router])
|
||||
|
||||
// Handle GitHub OAuth callback parameters
|
||||
// Handle VCS OAuth callback parameters (including private repo sync)
|
||||
useEffect(() => {
|
||||
if (isLoading || !user) return
|
||||
|
||||
const oauthSuccess = searchParams.get('oauth_success')
|
||||
const provider = searchParams.get('provider')
|
||||
const userId = searchParams.get('user_id')
|
||||
const syncPrivateRepo = searchParams.get('sync_private_repo')
|
||||
const repositoryUrl = searchParams.get('repository_url')
|
||||
const branchName = searchParams.get('branch_name')
|
||||
const syncStatus = searchParams.get('sync_status')
|
||||
|
||||
// Handle OAuth errors
|
||||
const oauthError = searchParams.get('oauth_error')
|
||||
const errorMessage = searchParams.get('error_message')
|
||||
|
||||
if (oauthError === 'true' && provider && errorMessage) {
|
||||
console.error(`❌ [Project Builder] OAuth error for ${provider}:`, errorMessage)
|
||||
|
||||
// Show error message to user
|
||||
alert(`OAuth Error for ${provider.toUpperCase()}:\n\n${decodeURIComponent(errorMessage)}\n\nPlease try again or contact support if the issue persists.`)
|
||||
|
||||
// Clear URL parameters
|
||||
const newUrl = new URL(window.location.href)
|
||||
newUrl.searchParams.delete('oauth_error')
|
||||
newUrl.searchParams.delete('provider')
|
||||
newUrl.searchParams.delete('error_message')
|
||||
newUrl.searchParams.delete('user_id')
|
||||
window.history.replaceState({}, '', newUrl.toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Handle new private repo sync flow
|
||||
if (oauthSuccess === 'true' && provider && syncPrivateRepo === 'true' && repositoryUrl) {
|
||||
console.log(`🔄 [Project Builder] Private repo sync initiated:`, {
|
||||
provider,
|
||||
repositoryUrl,
|
||||
branchName,
|
||||
syncStatus
|
||||
})
|
||||
|
||||
// Store sync info in sessionStorage for the main dashboard to pick up
|
||||
try {
|
||||
sessionStorage.setItem('private_repo_sync', JSON.stringify({
|
||||
provider,
|
||||
repositoryUrl,
|
||||
branchName,
|
||||
syncStatus,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
} catch (e) {
|
||||
console.warn('Failed to store sync info:', e)
|
||||
}
|
||||
|
||||
// Clear URL parameters
|
||||
const newUrl = new URL(window.location.href)
|
||||
newUrl.searchParams.delete('oauth_success')
|
||||
newUrl.searchParams.delete('provider')
|
||||
newUrl.searchParams.delete('user_id')
|
||||
newUrl.searchParams.delete('sync_private_repo')
|
||||
newUrl.searchParams.delete('repository_url')
|
||||
newUrl.searchParams.delete('branch_name')
|
||||
newUrl.searchParams.delete('sync_status')
|
||||
window.history.replaceState({}, '', newUrl.toString())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Handle legacy GitHub OAuth callback parameters
|
||||
const githubConnected = searchParams.get('github_connected')
|
||||
const githubUser = searchParams.get('user')
|
||||
const processing = searchParams.get('processing')
|
||||
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!', {
|
||||
|
||||
@ -23,7 +23,9 @@ import {
|
||||
Server
|
||||
} from 'lucide-react';
|
||||
import { authApiClient } from '@/components/apis/authApiClients';
|
||||
import { useAuth } from '@/contexts/auth-context';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface VcsRepoSummary {
|
||||
id: string;
|
||||
@ -44,6 +46,8 @@ interface VcsRepoSummary {
|
||||
}
|
||||
|
||||
const VcsReposPage: React.FC = () => {
|
||||
const { user, isLoading: authLoading } = useAuth();
|
||||
const router = useRouter();
|
||||
const [repositories, setRepositories] = useState<VcsRepoSummary[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -53,55 +57,62 @@ const VcsReposPage: React.FC = () => {
|
||||
const [aiAnalysisLoading, setAiAnalysisLoading] = useState<string | null>(null);
|
||||
const [aiAnalysisError, setAiAnalysisError] = useState<string | null>(null);
|
||||
|
||||
// Redirect to sign-in if not authenticated
|
||||
useEffect(() => {
|
||||
if (!authLoading && !user) {
|
||||
const returnUrl = encodeURIComponent(window.location.pathname + window.location.search);
|
||||
router.push(`/signin?returnUrl=${returnUrl}`);
|
||||
}
|
||||
}, [user, authLoading, router]);
|
||||
|
||||
// Handle OAuth callback for all providers
|
||||
useEffect(() => {
|
||||
const handleVcsCallback = async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get('code');
|
||||
const state = urlParams.get('state');
|
||||
const error = urlParams.get('error');
|
||||
const oauthSuccess = urlParams.get('oauth_success');
|
||||
const provider = urlParams.get('provider');
|
||||
const attachRepo = urlParams.get('attach_repo');
|
||||
const repositoryUrl = urlParams.get('repository_url');
|
||||
const branchName = urlParams.get('branch_name');
|
||||
|
||||
if (error) {
|
||||
console.error('VCS OAuth error:', error);
|
||||
alert(`OAuth error: ${error}`);
|
||||
return;
|
||||
}
|
||||
// Handle OAuth success redirect from backend
|
||||
if (oauthSuccess === 'true' && provider) {
|
||||
console.log(`🔐 [VCS OAuth] OAuth success for ${provider.toUpperCase()}`);
|
||||
|
||||
if (code && state) {
|
||||
console.log('🔐 [VCS OAuth] Callback received, processing...');
|
||||
if (attachRepo === 'true' && repositoryUrl) {
|
||||
console.log(`🔄 [VCS OAuth] Auto-attaching repository after OAuth: ${repositoryUrl}`);
|
||||
|
||||
try {
|
||||
// Check if we have a pending repository attachment
|
||||
const pendingAttach = sessionStorage.getItem('pending_git_attach');
|
||||
if (pendingAttach) {
|
||||
const { repository_url, branch_name, provider } = JSON.parse(pendingAttach);
|
||||
console.log(`🔐 [VCS OAuth] Resuming repository attachment:`, { repository_url, branch_name, provider });
|
||||
|
||||
// Clear the pending attach
|
||||
sessionStorage.removeItem('pending_git_attach');
|
||||
|
||||
// Now attach the repository
|
||||
try {
|
||||
// Automatically attach the repository
|
||||
const response = await authApiClient.post(`/api/vcs/${provider}/attach-repository`, {
|
||||
repository_url,
|
||||
branch_name: branch_name || undefined,
|
||||
repository_url: repositoryUrl,
|
||||
branch_name: branchName || undefined,
|
||||
user_id: user.id
|
||||
}, {
|
||||
headers: {
|
||||
'x-user-id': user.id
|
||||
}
|
||||
});
|
||||
|
||||
alert(`${provider?.toUpperCase()} account connected and repository attached successfully!`);
|
||||
if (response.data?.success) {
|
||||
alert(`✅ ${provider.toUpperCase()} account connected and repository attached successfully!`);
|
||||
|
||||
// Clean up URL parameters
|
||||
const newUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
|
||||
// Refresh the page to show the attached repository
|
||||
window.location.reload();
|
||||
// Refresh repositories to show the new one
|
||||
loadRepositories();
|
||||
} else {
|
||||
alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`);
|
||||
}
|
||||
} catch (attachError) {
|
||||
console.error('Failed to attach repository after OAuth:', attachError);
|
||||
alert(`${provider?.toUpperCase()} account connected, but failed to attach repository. Please try again.`);
|
||||
alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`);
|
||||
}
|
||||
} else {
|
||||
console.log('🔐 [VCS OAuth] No pending repository attachment, just connecting account');
|
||||
alert('Account connected successfully!');
|
||||
console.log(`🔐 [VCS OAuth] ${provider.toUpperCase()} account connected successfully`);
|
||||
alert(`${provider.toUpperCase()} account connected successfully!`);
|
||||
|
||||
// Clean up URL parameters
|
||||
const newUrl = window.location.pathname;
|
||||
@ -110,15 +121,47 @@ const VcsReposPage: React.FC = () => {
|
||||
// Refresh repositories
|
||||
loadRepositories();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling VCS OAuth callback:', error);
|
||||
alert('Error processing OAuth callback');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleVcsCallback();
|
||||
}, []);
|
||||
}, [user, loadRepositories]);
|
||||
|
||||
// Show loading while checking authentication
|
||||
if (authLoading) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto p-6">
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<RefreshCw className="h-8 w-8 animate-spin mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show sign-in prompt if not authenticated
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto p-6">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold mb-4">Authentication Required</h1>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
Please sign in to view your repositories from GitHub, GitLab, Bitbucket, and Gitea.
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<Button asChild size="lg">
|
||||
<Link href="/signin">Sign In to Continue</Link>
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
After signing in, you can connect your VCS accounts and view all your repositories in one place.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Load repositories from all providers
|
||||
const loadRepositories = async () => {
|
||||
@ -133,7 +176,11 @@ const VcsReposPage: React.FC = () => {
|
||||
|
||||
for (const provider of providers) {
|
||||
try {
|
||||
const response = await authApiClient.get(`/api/vcs/${provider}/repositories`);
|
||||
const response = await authApiClient.get(`/api/vcs/${provider}/repositories`, {
|
||||
headers: {
|
||||
'x-user-id': user.id
|
||||
}
|
||||
});
|
||||
const repos = response.data?.data || [];
|
||||
|
||||
// Add provider info to each repo
|
||||
|
||||
@ -27,6 +27,8 @@ import { attachRepository, detectProvider, connectProvider, AttachRepositoryResp
|
||||
import { getGitHubAuthStatus } from "@/lib/api/github"
|
||||
import ViewUserReposButton from "@/components/github/ViewUserReposButton"
|
||||
import { ErrorBanner } from "@/components/ui/error-banner"
|
||||
import { useAuth } from "@/contexts/auth-context"
|
||||
import { authApiClient } from "@/components/apis/authApiClients"
|
||||
|
||||
interface Template {
|
||||
id: string
|
||||
@ -53,6 +55,7 @@ interface Template {
|
||||
}
|
||||
|
||||
function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => void }) {
|
||||
const { user } = useAuth()
|
||||
const [selectedCategory, setSelectedCategory] = useState("all")
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [showCustomForm, setShowCustomForm] = useState(false)
|
||||
@ -70,11 +73,100 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
})
|
||||
const [authLoading, setAuthLoading] = useState(false)
|
||||
const [gitStep, setGitStep] = useState<'provider' | 'url'>('provider')
|
||||
const [authUrl, setAuthUrl] = useState('')
|
||||
const [isGeneratingAuth, setIsGeneratingAuth] = useState(false)
|
||||
|
||||
// Sync progress state for private repositories
|
||||
const [syncProgress, setSyncProgress] = useState<{
|
||||
show: boolean;
|
||||
provider: string;
|
||||
repositoryUrl: string;
|
||||
branchName: string;
|
||||
status: string;
|
||||
stage: string;
|
||||
} | null>(null)
|
||||
|
||||
// Monitor private repository sync progress
|
||||
const monitorPrivateRepoSync = async (provider: string, repositoryUrl: string) => {
|
||||
let pollCount = 0;
|
||||
const maxPolls = 60; // 2 minutes with 2-second intervals (reduced from 5 minutes)
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
pollCount++;
|
||||
|
||||
try {
|
||||
// Poll for repository sync status
|
||||
const response = await authApiClient.get(`/api/vcs/${provider}/repositories?t=${Date.now()}`, {
|
||||
headers: { 'x-user-id': user?.id }
|
||||
});
|
||||
|
||||
if (response.data?.success) {
|
||||
const repositories = response.data.data || [];
|
||||
const repo = repositories.find((r: any) => r.repository_url === repositoryUrl);
|
||||
|
||||
if (repo) {
|
||||
const status = repo.sync_status;
|
||||
let stage = '';
|
||||
|
||||
switch (status) {
|
||||
case 'authenticating':
|
||||
stage = 'Authenticating with provider...';
|
||||
break;
|
||||
case 'syncing':
|
||||
stage = 'Downloading repository files...';
|
||||
break;
|
||||
case 'synced':
|
||||
stage = 'Repository sync completed!';
|
||||
clearInterval(pollInterval);
|
||||
setSyncProgress(null);
|
||||
alert(`✅ Repository attached successfully!\n\nProvider: ${provider.toUpperCase()}\nRepository: ${repositoryUrl}\n\nYour repository is now available in your repositories list.`);
|
||||
// Refresh repositories list
|
||||
window.location.reload();
|
||||
return;
|
||||
case 'error':
|
||||
stage = 'Sync failed. Please try again.';
|
||||
clearInterval(pollInterval);
|
||||
setTimeout(() => {
|
||||
setSyncProgress(null);
|
||||
}, 3000);
|
||||
alert(`❌ Repository sync failed!\n\nProvider: ${provider.toUpperCase()}\nRepository: ${repositoryUrl}\n\nPlease try again or contact support.`);
|
||||
return;
|
||||
default:
|
||||
stage = 'Processing...';
|
||||
}
|
||||
|
||||
setSyncProgress(prev => prev ? { ...prev, status, stage } : null);
|
||||
} else {
|
||||
// Repository not found in list yet, continue polling
|
||||
setSyncProgress(prev => prev ? { ...prev, stage: 'Processing...' } : null);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error monitoring sync:', error);
|
||||
// If we get too many errors, stop polling
|
||||
if (pollCount > 10) {
|
||||
clearInterval(pollInterval);
|
||||
setSyncProgress(null);
|
||||
alert(`⚠️ Unable to monitor repository sync status.\n\nRepository: ${repositoryUrl}\n\nPlease check your repositories list manually.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Stop polling after max attempts
|
||||
if (pollCount >= maxPolls) {
|
||||
clearInterval(pollInterval);
|
||||
setSyncProgress(null);
|
||||
alert(`⏰ Repository sync is taking longer than expected.\n\nRepository: ${repositoryUrl}\n\nPlease check your repositories list manually.`);
|
||||
}
|
||||
}, 2000); // Poll every 2 seconds
|
||||
}
|
||||
const [isGithubConnected, setIsGithubConnected] = useState<boolean | null>(null)
|
||||
const [connectionError, setConnectionError] = useState<string | null>(null)
|
||||
|
||||
// Cleanup sync progress on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setSyncProgress(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
@ -94,119 +186,122 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
|
||||
// Handle OAuth callback for all providers with enhanced private repo flow
|
||||
useEffect(() => {
|
||||
const handleVcsCallback = async () => {
|
||||
// Check for private repo sync info from project-builder redirect
|
||||
try {
|
||||
const syncInfo = sessionStorage.getItem('private_repo_sync');
|
||||
if (syncInfo) {
|
||||
const { provider, repositoryUrl, branchName, syncStatus } = JSON.parse(syncInfo);
|
||||
console.log(`🔄 [Main Dashboard] Restoring private repo sync:`, { provider, repositoryUrl, syncStatus });
|
||||
|
||||
setSyncProgress({
|
||||
show: true,
|
||||
provider,
|
||||
repositoryUrl,
|
||||
branchName,
|
||||
status: syncStatus,
|
||||
stage: 'Starting sync...'
|
||||
});
|
||||
|
||||
// Start monitoring sync progress
|
||||
monitorPrivateRepoSync(provider, repositoryUrl);
|
||||
|
||||
// Clear the stored info
|
||||
sessionStorage.removeItem('private_repo_sync');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to restore sync info:', e);
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const oauthSuccess = urlParams.get('oauth_success');
|
||||
const provider = urlParams.get('provider');
|
||||
const syncPrivateRepo = urlParams.get('sync_private_repo');
|
||||
const repositoryUrl = urlParams.get('repository_url');
|
||||
const branchName = urlParams.get('branch_name');
|
||||
const syncStatus = urlParams.get('sync_status');
|
||||
|
||||
// Handle OAuth success redirect from backend
|
||||
if (oauthSuccess === 'true' && provider) {
|
||||
console.log(`🔐 [VCS OAuth] OAuth success for ${provider.toUpperCase()}`);
|
||||
|
||||
if (syncPrivateRepo === 'true' && repositoryUrl) {
|
||||
console.log(`🔄 [VCS OAuth] Starting private repository sync monitoring: ${repositoryUrl}`);
|
||||
|
||||
// Show progress notification for private repo sync
|
||||
setSyncProgress({
|
||||
show: true,
|
||||
provider: provider,
|
||||
repositoryUrl: repositoryUrl,
|
||||
branchName: branchName || 'main',
|
||||
status: syncStatus || 'authenticating',
|
||||
stage: 'Starting sync...'
|
||||
});
|
||||
|
||||
// Start monitoring sync progress
|
||||
monitorPrivateRepoSync(provider, repositoryUrl);
|
||||
|
||||
// Clean up URL parameters but keep sync progress visible
|
||||
const newUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
|
||||
} else {
|
||||
const attachRepo = urlParams.get('attach_repo');
|
||||
|
||||
if (attachRepo === 'true' && repositoryUrl) {
|
||||
console.log(`🔄 [VCS OAuth] Auto-attaching repository after OAuth: ${repositoryUrl}`);
|
||||
|
||||
try {
|
||||
// Automatically attach the repository
|
||||
const response = await authApiClient.post(`/api/vcs/${provider}/attach-repository`, {
|
||||
repository_url: repositoryUrl,
|
||||
branch_name: branchName || undefined,
|
||||
user_id: user?.id
|
||||
}, {
|
||||
headers: {
|
||||
'x-user-id': user?.id
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data?.success) {
|
||||
alert(`✅ ${provider.toUpperCase()} account connected and repository attached successfully!`);
|
||||
|
||||
// Clean up URL parameters
|
||||
const newUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
|
||||
// Reset form and close dialogs
|
||||
setShowGitForm(false);
|
||||
setShowCreateOptionDialog(false);
|
||||
setGitUrl('');
|
||||
setGitBranch('main');
|
||||
setGitProvider('');
|
||||
} else {
|
||||
alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`);
|
||||
}
|
||||
} catch (attachError) {
|
||||
console.error('Failed to attach repository after OAuth:', attachError);
|
||||
alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`);
|
||||
}
|
||||
} else {
|
||||
console.log(`🔐 [VCS OAuth] ${provider.toUpperCase()} account connected successfully`);
|
||||
alert(`${provider.toUpperCase()} account connected successfully!`);
|
||||
|
||||
// Clean up URL parameters
|
||||
const newUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleVcsCallback();
|
||||
}, [user])
|
||||
const [editingTemplate, setEditingTemplate] = useState<DatabaseTemplate | null>(null)
|
||||
|
||||
// Generate authentication by hitting the same attach endpoint and using its auth_url
|
||||
const generateAuthUrl = async () => {
|
||||
if (!gitUrl.trim()) return
|
||||
|
||||
setIsGeneratingAuth(true)
|
||||
try {
|
||||
// Persist pending attach so we can resume after OAuth
|
||||
try {
|
||||
sessionStorage.setItem('pending_git_attach', JSON.stringify({
|
||||
repository_url: gitUrl.trim(),
|
||||
branch_name: (gitBranch?.trim() || undefined)
|
||||
}))
|
||||
} catch {}
|
||||
|
||||
const result: AttachRepositoryResponse = await attachRepository({
|
||||
repository_url: gitUrl.trim(),
|
||||
branch_name: (gitBranch?.trim() || undefined),
|
||||
})
|
||||
|
||||
// Debug logging
|
||||
console.log('📦 Full result object:', result)
|
||||
console.log('📦 result.success value:', result?.success)
|
||||
console.log('📦 result.success type:', typeof result?.success)
|
||||
console.log('📦 Strict equality check:', result?.success === true)
|
||||
|
||||
// Check if response is successful
|
||||
if (result?.success !== true) {
|
||||
console.error('❌ Response indicates failure:', result)
|
||||
throw new Error('Repository attachment failed')
|
||||
}
|
||||
|
||||
const isPrivate = result?.data?.is_public === false
|
||||
|
||||
console.log('✅ Repository attached successfully:', result)
|
||||
const repoType = isPrivate ? 'private' : 'public'
|
||||
alert(`Repository attached successfully! (${repoType}) You can now proceed with your project.`)
|
||||
setShowCreateOptionDialog(false)
|
||||
setShowGitForm(false)
|
||||
setGitProvider('')
|
||||
setGitUrl('')
|
||||
setGitBranch('main')
|
||||
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status
|
||||
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('❌ Error attaching repository:', {
|
||||
status,
|
||||
data,
|
||||
message: err?.message,
|
||||
code: err?.code,
|
||||
url: gitUrl.trim()
|
||||
})
|
||||
|
||||
if (status === 401 && (data?.requires_auth || data?.auth_url || data?.service_auth_url)) {
|
||||
console.log('🔐 Private repository detected - initiating OAuth with repository context')
|
||||
// Reset loading state before redirect
|
||||
setIsGeneratingAuth(false)
|
||||
|
||||
// Detect provider from URL
|
||||
const detectedProvider = detectProvider(gitUrl.trim());
|
||||
|
||||
// Use the universal OAuth helper that will auto-attach the repo after authentication
|
||||
setTimeout(() => {
|
||||
connectProvider(detectedProvider, gitUrl.trim(), gitBranch?.trim() || 'main').catch((oauthError) => {
|
||||
console.error('OAuth initiation failed:', oauthError)
|
||||
alert(`Failed to initiate ${detectedProvider.toUpperCase()} authentication. Please try again.`)
|
||||
})
|
||||
}, 100)
|
||||
return
|
||||
}
|
||||
|
||||
if (status === 403) {
|
||||
// Reset loading state before showing dialog
|
||||
setIsGeneratingAuth(false)
|
||||
|
||||
// Repository not accessible with current account - prompt to re-authenticate
|
||||
setTimeout(() => {
|
||||
// Detect provider from URL
|
||||
const detectedProvider = detectProvider(gitUrl.trim());
|
||||
|
||||
const confirmReauth = confirm(`Repository not accessible with your current ${detectedProvider.toUpperCase()} account.\n\nThis could mean:\n- The repository belongs to a different ${detectedProvider.toUpperCase()} account\n- Your token expired or lacks permissions\n\nWould you like to re-authenticate with ${detectedProvider.toUpperCase()}?`)
|
||||
|
||||
if (confirmReauth) {
|
||||
console.log(`🔐 Re-authenticating with ${detectedProvider.toUpperCase()} for private repository access`)
|
||||
connectProvider(detectedProvider, gitUrl.trim(), gitBranch?.trim() || 'main').catch((oauthError) => {
|
||||
console.error('OAuth initiation failed:', oauthError)
|
||||
alert(`Failed to initiate ${detectedProvider.toUpperCase()} authentication. Please try again.`)
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
return
|
||||
}
|
||||
|
||||
if (status === 404) {
|
||||
alert('Repository not found - please check the URL and try again')
|
||||
return
|
||||
}
|
||||
|
||||
console.error('❌ Full error details:', err)
|
||||
const errorMessage = data?.message || err?.message || 'Failed to attach repository. Please check the URL and try again.'
|
||||
alert(errorMessage)
|
||||
} finally {
|
||||
setIsGeneratingAuth(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication handlers for different providers
|
||||
const handleOAuthAuth = async (provider: string) => {
|
||||
@ -309,13 +404,13 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
oauthEndpoint: '/api/vcs/gitlab/auth/start',
|
||||
apiEndpoint: 'https://gitlab.com/api/v4'
|
||||
},
|
||||
other: {
|
||||
name: 'Other Git',
|
||||
gitea: {
|
||||
name: 'Gitea',
|
||||
icon: 'M12 0C5.374 0 0 5.373 0 12s5.374 12 12 12 12-5.373 12-12S18.626 0 12 0zm0 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-6h2v2h-2v-2zm0-8h2v6h-2V8z',
|
||||
placeholder: 'https://your-git-server.com/org/repo.git',
|
||||
authMethods: ['username_password', 'token', 'ssh'],
|
||||
oauthEndpoint: null,
|
||||
apiEndpoint: null
|
||||
placeholder: 'https://gitea.com/org/repo.git',
|
||||
authMethods: ['token', 'oauth', 'ssh'],
|
||||
oauthEndpoint: '/api/vcs/gitea/auth/start',
|
||||
apiEndpoint: 'https://gitea.com/api/v1'
|
||||
}
|
||||
}
|
||||
const [deletingTemplate, setDeletingTemplate] = useState<DatabaseTemplate | null>(null)
|
||||
@ -331,7 +426,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
const [descDialogData, setDescDialogData] = useState<{ title: string; description: string }>({ title: '', description: '' })
|
||||
|
||||
const {
|
||||
user,
|
||||
user: templateUser,
|
||||
combined,
|
||||
loading,
|
||||
error,
|
||||
@ -529,7 +624,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
is_custom: true,
|
||||
source: 'custom',
|
||||
// Attach user id so custom template is associated with creator
|
||||
user_id: (user as any)?.id,
|
||||
user_id: (templateUser as any)?.id,
|
||||
} as any);
|
||||
setShowCustomForm(false);
|
||||
return created;
|
||||
@ -552,13 +647,62 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
console.log('🔍 [handleCreateFromGit] Detected provider:', detectedProvider);
|
||||
console.log('🔍 [handleCreateFromGit] Git URL:', gitUrl.trim());
|
||||
|
||||
// Check if repository already exists for this user
|
||||
try {
|
||||
const existingReposResponse = await authApiClient.get(`/api/vcs/${detectedProvider}/repositories?t=${Date.now()}`, {
|
||||
headers: { 'x-user-id': user?.id }
|
||||
});
|
||||
|
||||
if (existingReposResponse.data?.success) {
|
||||
const repositories = existingReposResponse.data.data || [];
|
||||
const existingRepo = repositories.find((r: any) => r.repository_url === gitUrl.trim());
|
||||
|
||||
if (existingRepo) {
|
||||
alert(`✅ Repository already exists!\n\nProvider: ${detectedProvider.toUpperCase()}\nRepository: ${gitUrl.trim()}\nStatus: ${existingRepo.sync_status}\n\nThis repository is already available in your repositories list.`);
|
||||
setShowCreateOptionDialog(false);
|
||||
setShowGitForm(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('🔍 [handleCreateFromGit] Could not check existing repositories, proceeding with attachment:', error);
|
||||
// Continue with attachment even if we can't check existing repos
|
||||
}
|
||||
|
||||
// Attach the repository via backend (skip template creation)
|
||||
try {
|
||||
await attachRepository({
|
||||
const attachResult = await attachRepository({
|
||||
repository_url: gitUrl.trim(),
|
||||
branch_name: (gitBranch?.trim() || undefined),
|
||||
})
|
||||
|
||||
// Check if authentication is required
|
||||
if (attachResult.requires_auth) {
|
||||
console.log('🔐 Private repository detected, auto-redirecting to OAuth:', attachResult)
|
||||
|
||||
// Store the auth_url for OAuth redirect
|
||||
try {
|
||||
sessionStorage.setItem('pending_git_attach', JSON.stringify({
|
||||
repository_url: gitUrl.trim(),
|
||||
branch_name: (gitBranch?.trim() || undefined),
|
||||
provider: detectedProvider,
|
||||
auth_url: attachResult.auth_url
|
||||
}))
|
||||
console.log('💾 Stored pending git attach with auth_url in sessionStorage')
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Failed to store pending git attach:', e)
|
||||
}
|
||||
|
||||
// Auto-redirect to OAuth
|
||||
console.log('🔐 Private repository detected - auto-redirecting to OAuth:', attachResult.auth_url)
|
||||
if (attachResult.auth_url) {
|
||||
window.location.replace(attachResult.auth_url)
|
||||
} else {
|
||||
alert('Authentication URL not available. Please try again.')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Show success message and reset form
|
||||
alert('Repository attached successfully! You can now proceed with your project.');
|
||||
setShowCreateOptionDialog(false)
|
||||
@ -588,28 +732,33 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
const data = err?.response?.data
|
||||
|
||||
console.log('🔍 HandleCreateFromGit error response:', { status, data })
|
||||
console.log('🔍 Error details:', {
|
||||
message: err?.message,
|
||||
requires_auth: data?.requires_auth,
|
||||
auth_url: data?.auth_url
|
||||
})
|
||||
console.log('🔍 Full error object:', err)
|
||||
console.log('🔍 Error response object:', err?.response)
|
||||
|
||||
// If backend signals auth required, redirect to provider OAuth
|
||||
// If backend signals auth required, show authentication button instead of auto-redirect
|
||||
if ((status === 401 || status === 200) && (data?.requires_auth || data?.message?.includes('authentication'))) {
|
||||
const authUrl: string = data?.auth_url
|
||||
if (!authUrl) {
|
||||
alert('Authentication URL is missing.');
|
||||
return
|
||||
}
|
||||
console.log('🔐 Private repository detected, showing authentication button:', { status, requires_auth: data?.requires_auth, message: data?.message })
|
||||
|
||||
// Persist pending repo so we resume after OAuth callback
|
||||
// Store the auth_url for when user clicks the authenticate button
|
||||
try {
|
||||
sessionStorage.setItem('pending_git_attach', JSON.stringify({
|
||||
repository_url: gitUrl.trim(),
|
||||
branch_name: (gitBranch?.trim() || undefined),
|
||||
provider: detectedProvider
|
||||
provider: detectedProvider,
|
||||
auth_url: data?.auth_url
|
||||
}))
|
||||
} catch {}
|
||||
console.log('💾 Stored pending git attach with auth_url in sessionStorage')
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Failed to store pending git attach:', e)
|
||||
}
|
||||
|
||||
console.log(`🔐 Redirecting to ${detectedProvider.toUpperCase()} OAuth for repository attachment:`, authUrl)
|
||||
console.log(`🔐 Provider being passed to connectProvider:`, detectedProvider)
|
||||
// Use connectProvider function to handle OAuth flow properly
|
||||
await connectProvider(detectedProvider, gitUrl.trim(), gitBranch?.trim())
|
||||
// Don't auto-redirect, let the user click the authenticate button
|
||||
console.log('🔐 Private repository detected - user must click authenticate button')
|
||||
return
|
||||
}
|
||||
|
||||
@ -618,6 +767,20 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback: if we have an auth_url in the error, try to redirect anyway
|
||||
if (data?.auth_url && !data?.success) {
|
||||
console.log('🔐 Fallback OAuth redirect - auth_url found in error response:', data.auth_url)
|
||||
window.location.replace(data.auth_url)
|
||||
return
|
||||
}
|
||||
|
||||
// Additional fallback: check if the error message contains auth_url
|
||||
if (err?.message && err.message.includes('authentication') && data?.auth_url) {
|
||||
console.log('🔐 Additional fallback OAuth redirect - authentication message with auth_url:', data.auth_url)
|
||||
window.location.replace(data.auth_url)
|
||||
return
|
||||
}
|
||||
|
||||
if (status === 404) {
|
||||
alert('Repository not found - please check the URL and try again')
|
||||
return
|
||||
@ -814,6 +977,43 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
{/* Sync Progress Notification */}
|
||||
{syncProgress?.show && (
|
||||
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-4 mb-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-orange-500"></div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-white">
|
||||
Syncing {syncProgress.provider.toUpperCase()} Repository
|
||||
</h3>
|
||||
<p className="text-white/80 text-sm">
|
||||
{syncProgress.stage}
|
||||
</p>
|
||||
<div className="mt-2 text-xs text-white/60">
|
||||
Repository: {syncProgress.repositoryUrl}
|
||||
{syncProgress.branchName !== 'main' && ` | Branch: ${syncProgress.branchName}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className={`px-2 py-1 rounded text-xs font-medium ${
|
||||
syncProgress.status === 'synced' ? 'bg-green-500/20 text-green-400' :
|
||||
syncProgress.status === 'error' ? 'bg-red-500/20 text-red-400' :
|
||||
'bg-orange-500/20 text-orange-400'
|
||||
}`}>
|
||||
{syncProgress.status}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSyncProgress(null)}
|
||||
className="text-white/60 hover:text-white/80 transition-colors"
|
||||
title="Dismiss sync progress"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-3">
|
||||
<h1 className="text-4xl font-bold text-white">Choose Your Project Template</h1>
|
||||
@ -844,7 +1044,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!user?.id && (
|
||||
{!templateUser?.id && (
|
||||
<div className="bg-orange-500/10 border border-orange-500/20 rounded-lg p-4 max-w-2xl mx-auto">
|
||||
<p className="text-orange-300 text-sm">
|
||||
You're currently viewing public templates.
|
||||
@ -867,12 +1067,25 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
{/* Right-aligned quick navigation to user repos */}
|
||||
<div className="flex justify-end space-x-2">
|
||||
<ViewUserReposButton className="bg-orange-500 hover:bg-orange-400 text-black" label="My GitHub Repos" />
|
||||
{templateUser?.id ? (
|
||||
<Link href="/vcs/repos">
|
||||
<Button className="bg-blue-500 hover:bg-blue-400 text-white">
|
||||
<FolderGit2 className="mr-2 h-5 w-5" />
|
||||
All My Repos
|
||||
</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => {
|
||||
const returnUrl = encodeURIComponent('/vcs/repos');
|
||||
window.location.href = `/signin?returnUrl=${returnUrl}`;
|
||||
}}
|
||||
className="bg-blue-500 hover:bg-blue-400 text-white"
|
||||
>
|
||||
<FolderGit2 className="mr-2 h-5 w-5" />
|
||||
All My Repos
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
@ -1215,7 +1428,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
</div>
|
||||
|
||||
<Card className="group border-dashed border-2 border-white/15 bg-white/5 hover:border-white/25 transition-all cursor-pointer" onClick={() => {
|
||||
if (!user?.id) { window.location.href = '/signin'; return }
|
||||
if (!templateUser?.id) { window.location.href = '/signin'; return }
|
||||
setShowCreateOptionDialog(true)
|
||||
}}>
|
||||
<CardContent className="text-center py-16 px-8 text-white/80">
|
||||
@ -1223,20 +1436,20 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
<Plus className="h-10 w-10 text-orange-400" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-white mb-3">
|
||||
{user?.id ? 'Create Custom Template' : 'Sign In to Create Templates'}
|
||||
{templateUser?.id ? 'Create Custom Template' : 'Sign In to Create Templates'}
|
||||
</h3>
|
||||
<p className="mb-8 max-w-md mx-auto text-lg leading-relaxed">
|
||||
{user?.id
|
||||
{templateUser?.id
|
||||
? "Don't worry, we'll guide you through each step. a custom project type with your specific requirements and tech stack."
|
||||
: "Sign in to create custom project templates with your specific requirements and tech stack."
|
||||
}
|
||||
</p>
|
||||
<Button variant="outline" className="border-white/20 text-white hover:bg-white/10" onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (!user?.id) { window.location.href = '/signin'; return }
|
||||
if (!templateUser?.id) { window.location.href = '/signin'; return }
|
||||
setShowCreateOptionDialog(true)
|
||||
}}>
|
||||
{user?.id ? (
|
||||
{templateUser?.id ? (
|
||||
<>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
Create Custom Template
|
||||
@ -1293,7 +1506,6 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
setGitAuthMethod('')
|
||||
setGitCredentials({ username: '', password: '', token: '', sshKey: '' })
|
||||
setGitStep('provider')
|
||||
setAuthUrl('')
|
||||
}
|
||||
}}>
|
||||
<DialogContent className="bg-white/10 border-white/20 text-white" showCloseButton>
|
||||
@ -1363,7 +1575,9 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
<label className="block text-sm text-white/70 mb-1">Repository URL</label>
|
||||
<Input
|
||||
value={gitUrl}
|
||||
onChange={(e) => setGitUrl(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setGitUrl(e.target.value)
|
||||
}}
|
||||
placeholder={gitProviders[gitProvider as keyof typeof gitProviders]?.placeholder}
|
||||
className="bg-white/10 border-white/20 text-white"
|
||||
/>
|
||||
@ -1385,62 +1599,6 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Authentication URL Generation: show only if not already connected */}
|
||||
{gitUrl.trim() && isGithubConnected === false && (
|
||||
<div className="space-y-3">
|
||||
<div className="p-4 bg-blue-500/10 border border-blue-500/20 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-blue-200 text-sm mb-3">
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Ready to authenticate with {gitProviders[gitProvider as keyof typeof gitProviders]?.name}
|
||||
</div>
|
||||
<p className="text-xs text-blue-200/80 mb-3">
|
||||
Click the button below to generate an authentication URL. This will open a new window where you can securely authenticate with your {gitProviders[gitProvider as keyof typeof gitProviders]?.name} account.
|
||||
</p>
|
||||
<Button
|
||||
className="w-full bg-blue-500 hover:bg-blue-400 text-white"
|
||||
type="button"
|
||||
onClick={(e) => { e.preventDefault(); generateAuthUrl(); }}
|
||||
disabled={isGeneratingAuth || !gitUrl.trim()}
|
||||
>
|
||||
{isGeneratingAuth ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Generating Auth URL...
|
||||
</div>
|
||||
) : (
|
||||
`Authenticate with ${gitProviders[gitProvider as keyof typeof gitProviders]?.name}`
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{authUrl && (
|
||||
<div className="p-3 bg-green-500/10 border border-green-500/20 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-green-200 text-sm mb-2">
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Authentication URL Generated
|
||||
</div>
|
||||
<p className="text-xs text-green-200/80 mb-2">
|
||||
If the authentication window didn't open, you can click the link below:
|
||||
</p>
|
||||
<a
|
||||
href={authUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-blue-300 hover:text-blue-200 underline break-all"
|
||||
>
|
||||
{authUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 justify-end pt-2">
|
||||
<Button variant="outline" className="border-white/20 text-white hover:bg-white/10" onClick={() => setGitStep('provider')}>Back</Button>
|
||||
@ -1448,7 +1606,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
className="bg-orange-500 hover:bg-orange-400 text-black"
|
||||
type="button"
|
||||
onClick={(e) => { e.preventDefault(); handleCreateFromGit(); }}
|
||||
disabled={!gitUrl.trim() || (isGithubConnected === false && !authUrl)}
|
||||
disabled={!gitUrl.trim()}
|
||||
>
|
||||
Import Template
|
||||
</Button>
|
||||
|
||||
@ -50,6 +50,22 @@ export async function attachRepository(payload: AttachRepositoryPayload, retries
|
||||
const provider = detectProvider(payload.repository_url);
|
||||
console.log('🔍 [attachRepository] Detected provider:', provider);
|
||||
|
||||
// First, try to check if user already has a valid token for this provider
|
||||
if (userId) {
|
||||
try {
|
||||
console.log(`🔍 [attachRepository] Checking for existing ${provider.toUpperCase()} token for user:`, userId);
|
||||
const tokenCheckResponse = await authApiClient.get(`/api/vcs/${provider}/repositories`, {
|
||||
headers: { 'x-user-id': userId }
|
||||
});
|
||||
|
||||
if (tokenCheckResponse.data?.success) {
|
||||
console.log(`✅ [attachRepository] Found existing ${provider.toUpperCase()} token, proceeding with attachment`);
|
||||
}
|
||||
} catch (tokenError) {
|
||||
console.log(`🔍 [attachRepository] No existing ${provider.toUpperCase()} token found, will require OAuth`);
|
||||
}
|
||||
}
|
||||
|
||||
const url = userId
|
||||
? `/api/vcs/${provider}/attach-repository?user_id=${encodeURIComponent(userId)}`
|
||||
: `/api/vcs/${provider}/attach-repository`;
|
||||
@ -82,6 +98,18 @@ export async function attachRepository(payload: AttachRepositoryPayload, retries
|
||||
success: (parsed?.success === true || parsed?.success === 'true')
|
||||
};
|
||||
|
||||
// If authentication is required, return the response instead of throwing error
|
||||
if (normalized.requires_auth || (normalized.message && normalized.message.includes('authentication'))) {
|
||||
console.log('🔐 [attachRepository] Authentication required, returning response for UI to show authenticate button:', {
|
||||
requires_auth: normalized.requires_auth,
|
||||
message: normalized.message,
|
||||
auth_url: normalized.auth_url
|
||||
});
|
||||
|
||||
// Return the response so UI can show authenticate button
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
} catch (error: any) {
|
||||
// If it's the last retry or not a connection error, throw immediately
|
||||
|
||||
Loading…
Reference in New Issue
Block a user