diff --git a/src/app/project-builder/page.tsx b/src/app/project-builder/page.tsx index 919654a..9e34a88 100644 --- a/src/app/project-builder/page.tsx +++ b/src/app/project-builder/page.tsx @@ -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!', { diff --git a/src/app/vcs/repos/page.tsx b/src/app/vcs/repos/page.tsx index 45f2a1d..40da7c0 100644 --- a/src/app/vcs/repos/page.tsx +++ b/src/app/vcs/repos/page.tsx @@ -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([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -53,72 +57,111 @@ const VcsReposPage: React.FC = () => { const [aiAnalysisLoading, setAiAnalysisLoading] = useState(null); const [aiAnalysisError, setAiAnalysisError] = useState(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; - } - - if (code && state) { - console.log('🔐 [VCS OAuth] Callback received, processing...'); + // Handle OAuth success redirect from backend + if (oauthSuccess === 'true' && provider) { + console.log(`🔐 [VCS OAuth] OAuth success for ${provider.toUpperCase()}`); - 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 }); + 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 + } + }); - // Clear the pending attach - sessionStorage.removeItem('pending_git_attach'); - - // Now attach the repository - try { - const response = await authApiClient.post(`/api/vcs/${provider}/attach-repository`, { - repository_url, - branch_name: branch_name || undefined, - }); - - 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(); - } catch (attachError) { - console.error('Failed to attach repository after OAuth:', attachError); - alert(`${provider?.toUpperCase()} account connected, but failed to attach repository. Please try again.`); + // Refresh repositories to show the new one + loadRepositories(); + } else { + 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!'); - - // Clean up URL parameters - const newUrl = window.location.pathname; - window.history.replaceState({}, document.title, newUrl); - - // Refresh repositories - loadRepositories(); + } catch (attachError) { + console.error('Failed to attach repository after OAuth:', attachError); + alert(`${provider.toUpperCase()} account connected, but failed to attach repository. Please try again.`); } - } catch (error) { - console.error('Error handling VCS OAuth callback:', error); - alert('Error processing OAuth callback'); + } 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); + + // Refresh repositories + loadRepositories(); } } }; handleVcsCallback(); - }, []); + }, [user, loadRepositories]); + + // Show loading while checking authentication + if (authLoading) { + return ( +
+
+
+ +

Loading...

+
+
+
+ ); + } + + // Show sign-in prompt if not authenticated + if (!user) { + return ( +
+
+

Authentication Required

+

+ Please sign in to view your repositories from GitHub, GitLab, Bitbucket, and Gitea. +

+
+ +
+ After signing in, you can connect your VCS accounts and view all your repositories in one place. +
+
+
+
+ ); + } // 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 diff --git a/src/components/main-dashboard.tsx b/src/components/main-dashboard.tsx index 985d060..13f21e8 100644 --- a/src/components/main-dashboard.tsx +++ b/src/components/main-dashboard.tsx @@ -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,10 +73,99 @@ 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(null) const [connectionError, setConnectionError] = useState(null) + + // Cleanup sync progress on component unmount + useEffect(() => { + return () => { + setSyncProgress(null); + }; + }, []); useEffect(() => { (async () => { @@ -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(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(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,13 +977,50 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi return (
+ {/* Sync Progress Notification */} + {syncProgress?.show && ( +
+
+
+
+

+ Syncing {syncProgress.provider.toUpperCase()} Repository +

+

+ {syncProgress.stage} +

+
+ Repository: {syncProgress.repositoryUrl} + {syncProgress.branchName !== 'main' && ` | Branch: ${syncProgress.branchName}`} +
+
+
+
+ {syncProgress.status} +
+ +
+
+
+ )} + {/* Header */}

Choose Your Project Template

Select from our comprehensive library of professionally designed templates

- + {/* Connection Error Banner */} {connectionError && (
@@ -844,7 +1044,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi />
)} - {!user?.id && ( + {!templateUser?.id && (

You're currently viewing public templates. @@ -867,12 +1067,25 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi {/* Right-aligned quick navigation to user repos */}

- - + + ) : ( + - + )}
@@ -1215,7 +1428,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
{ - if (!user?.id) { window.location.href = '/signin'; return } + if (!templateUser?.id) { window.location.href = '/signin'; return } setShowCreateOptionDialog(true) }}> @@ -1223,20 +1436,20 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi

- {user?.id ? 'Create Custom Template' : 'Sign In to Create Templates'} + {templateUser?.id ? 'Create Custom Template' : 'Sign In to Create Templates'}

- {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." }

- {/* Authentication URL Generation: show only if not already connected */} - {gitUrl.trim() && isGithubConnected === false && ( -
-
-
- - - - Ready to authenticate with {gitProviders[gitProvider as keyof typeof gitProviders]?.name} -
-

- 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. -

- -
- - {authUrl && ( -
-
- - - - Authentication URL Generated -
-

- If the authentication window didn't open, you can click the link below: -

- - {authUrl} - -
- )} -
- )}
@@ -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 diff --git a/src/lib/api/vcs.ts b/src/lib/api/vcs.ts index 091d07c..8d9a001 100644 --- a/src/lib/api/vcs.ts +++ b/src/lib/api/vcs.ts @@ -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