frontend changes
This commit is contained in:
parent
797f2e8657
commit
69def8560b
@ -95,6 +95,19 @@ const addAuthTokenInterceptor = (client: typeof authApiClient) => {
|
||||
(config) => {
|
||||
// Always get fresh token from localStorage instead of using module variable
|
||||
const freshToken = getAccessToken();
|
||||
// Attach user_id for backend routing that requires it
|
||||
try {
|
||||
const rawUser = safeLocalStorage.getItem('codenuk_user');
|
||||
if (rawUser) {
|
||||
const parsed = JSON.parse(rawUser);
|
||||
const userId = parsed?.id;
|
||||
if (userId) {
|
||||
config.headers = config.headers || {};
|
||||
// Header preferred by backend
|
||||
(config.headers as any)['x-user-id'] = userId;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
if (freshToken) {
|
||||
config.headers = config.headers || {};
|
||||
config.headers.Authorization = `Bearer ${freshToken}`;
|
||||
@ -110,6 +123,13 @@ const addTokenRefreshInterceptor = (client: typeof authApiClient) => {
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
// Surface detailed server error info in the console for debugging
|
||||
try {
|
||||
const status = error?.response?.status
|
||||
const data = error?.response?.data
|
||||
console.error('🛑 API error:', { url: error?.config?.url, method: error?.config?.method, status, data })
|
||||
} catch (_) {}
|
||||
|
||||
const originalRequest = error.config;
|
||||
const isRefreshEndpoint = originalRequest?.url?.includes('/api/auth/refresh');
|
||||
if (error.response?.status === 401 && !originalRequest._retry && !isRefreshEndpoint) {
|
||||
|
||||
@ -22,6 +22,7 @@ import PromptSidePanel from "@/components/prompt-side-panel"
|
||||
import { DualCanvasEditor } from "@/components/dual-canvas-editor"
|
||||
import { getAccessToken } from "@/components/apis/authApiClients"
|
||||
import TechStackSummary from "@/components/tech-stack-summary"
|
||||
import { attachRepository, getGitHubAuthStatus } from "@/lib/api/github"
|
||||
|
||||
interface Template {
|
||||
id: string
|
||||
@ -51,7 +52,188 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
const [selectedCategory, setSelectedCategory] = useState("all")
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [showCustomForm, setShowCustomForm] = useState(false)
|
||||
const [showCreateOptionDialog, setShowCreateOptionDialog] = useState(false)
|
||||
const [showGitForm, setShowGitForm] = useState(false)
|
||||
const [gitProvider, setGitProvider] = useState('')
|
||||
const [gitUrl, setGitUrl] = useState('')
|
||||
const [gitBranch, setGitBranch] = useState('main')
|
||||
const [gitAuthMethod, setGitAuthMethod] = useState('')
|
||||
const [gitCredentials, setGitCredentials] = useState({
|
||||
username: '',
|
||||
password: '',
|
||||
token: '',
|
||||
sshKey: ''
|
||||
})
|
||||
const [authLoading, setAuthLoading] = useState(false)
|
||||
const [gitStep, setGitStep] = useState<'provider' | 'url'>('provider')
|
||||
const [authUrl, setAuthUrl] = useState('')
|
||||
const [isGeneratingAuth, setIsGeneratingAuth] = useState(false)
|
||||
const [isGithubConnected, setIsGithubConnected] = useState<boolean | null>(null)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const status = await getGitHubAuthStatus()
|
||||
setIsGithubConnected(!!status?.data?.connected)
|
||||
} catch {
|
||||
setIsGithubConnected(false)
|
||||
}
|
||||
})()
|
||||
}, [])
|
||||
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 {}
|
||||
|
||||
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.
|
||||
} 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
|
||||
if (url) {
|
||||
window.location.replace(url)
|
||||
return
|
||||
}
|
||||
}
|
||||
console.error('Error generating auth URL via attach:', err)
|
||||
alert(data?.message || 'Failed to generate authentication URL. Please try again.')
|
||||
} finally {
|
||||
setIsGeneratingAuth(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication handlers for different providers
|
||||
const handleOAuthAuth = async (provider: string) => {
|
||||
setAuthLoading(true)
|
||||
try {
|
||||
const providerConfig = gitProviders[provider as keyof typeof gitProviders]
|
||||
if (!providerConfig.oauthEndpoint) {
|
||||
throw new Error('OAuth not supported for this provider')
|
||||
}
|
||||
|
||||
// Redirect to OAuth endpoint
|
||||
window.open(providerConfig.oauthEndpoint, '_blank', 'width=600,height=700')
|
||||
|
||||
// In a real implementation, you'd handle the OAuth callback
|
||||
// and store the access token
|
||||
alert(`Redirecting to ${providerConfig.name} OAuth...`)
|
||||
} catch (error) {
|
||||
console.error('OAuth error:', error)
|
||||
alert('OAuth authentication failed')
|
||||
} finally {
|
||||
setAuthLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleTokenAuth = async (provider: string, token: string) => {
|
||||
setAuthLoading(true)
|
||||
try {
|
||||
const providerConfig = gitProviders[provider as keyof typeof gitProviders]
|
||||
|
||||
// Validate token with provider API
|
||||
const response = await fetch(`${providerConfig.apiEndpoint}/user`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Invalid token')
|
||||
}
|
||||
|
||||
const userData = await response.json()
|
||||
alert(`Authenticated as ${userData.login || userData.username || userData.name}`)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Token auth error:', error)
|
||||
alert('Token authentication failed')
|
||||
return false
|
||||
} finally {
|
||||
setAuthLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUsernamePasswordAuth = async (provider: string, username: string, password: string) => {
|
||||
setAuthLoading(true)
|
||||
try {
|
||||
// For username/password auth, you'd typically use Basic Auth
|
||||
// or convert to token-based auth
|
||||
const credentials = btoa(`${username}:${password}`)
|
||||
|
||||
const response = await fetch(`${gitProviders[provider as keyof typeof gitProviders].apiEndpoint}/user`, {
|
||||
headers: {
|
||||
'Authorization': `Basic ${credentials}`,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Invalid credentials')
|
||||
}
|
||||
|
||||
const userData = await response.json()
|
||||
alert(`Authenticated as ${userData.login || userData.username || userData.name}`)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Username/password auth error:', error)
|
||||
alert('Authentication failed')
|
||||
return false
|
||||
} finally {
|
||||
setAuthLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Provider-specific configuration
|
||||
const gitProviders = {
|
||||
github: {
|
||||
name: 'GitHub',
|
||||
icon: 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z',
|
||||
placeholder: 'https://github.com/org/repo.git',
|
||||
authMethods: ['token', 'ssh', 'oauth'],
|
||||
oauthEndpoint: '/api/auth/github',
|
||||
apiEndpoint: 'https://api.github.com'
|
||||
},
|
||||
bitbucket: {
|
||||
name: 'Bitbucket',
|
||||
icon: 'M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891L.778 1.213zM14.518 18.197H9.482l-1.4-8.893h7.436l-1.4 8.893z',
|
||||
placeholder: 'https://bitbucket.org/org/repo.git',
|
||||
authMethods: ['username_password', 'app_password', 'oauth'],
|
||||
oauthEndpoint: '/api/auth/bitbucket',
|
||||
apiEndpoint: 'https://api.bitbucket.org/2.0'
|
||||
},
|
||||
gitlab: {
|
||||
name: 'GitLab',
|
||||
icon: 'M23.6004 9.5927l-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.874.874 0 0 0-.9997.0539.874.874 0 0 0-.29.4399l-2.5465 7.7838H7.2162l-2.5465-7.7838a.857.857 0 0 0-.29-.4412.874.874 0 0 0-.9997-.0537.858.858 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.065 6.065 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.008 1.008 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7466.0125-.01a6.068 6.068 0 0 0 2.0094-7.003z',
|
||||
placeholder: 'https://gitlab.com/org/repo.git',
|
||||
authMethods: ['token', 'oauth', 'ssh'],
|
||||
oauthEndpoint: '/api/auth/gitlab',
|
||||
apiEndpoint: 'https://gitlab.com/api/v4'
|
||||
},
|
||||
other: {
|
||||
name: 'Other Git',
|
||||
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
|
||||
}
|
||||
}
|
||||
const [deletingTemplate, setDeletingTemplate] = useState<DatabaseTemplate | null>(null)
|
||||
const [deleteLoading, setDeleteLoading] = useState(false)
|
||||
// Keep a stable list of all categories seen so the filter chips don't disappear
|
||||
@ -274,6 +456,74 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateFromGit = async () => {
|
||||
try {
|
||||
if (!gitUrl.trim()) {
|
||||
alert('Please enter a Git repository URL');
|
||||
return;
|
||||
}
|
||||
// Attach the repository via backend (skip template creation)
|
||||
try {
|
||||
await attachRepository({
|
||||
repository_url: gitUrl.trim(),
|
||||
branch_name: (gitBranch?.trim() || undefined),
|
||||
})
|
||||
|
||||
// Show success message and reset form
|
||||
alert('Repository attached successfully! You can now proceed with your project.');
|
||||
setShowCreateOptionDialog(false)
|
||||
setShowGitForm(false)
|
||||
setGitProvider('')
|
||||
setGitUrl('')
|
||||
setGitBranch('main')
|
||||
|
||||
// Return a mock template object to proceed to next step
|
||||
return {
|
||||
id: 'git-imported',
|
||||
title: `Imported from ${gitProvider === 'other' ? 'Git' : gitProvider.charAt(0).toUpperCase() + gitProvider.slice(1)}`,
|
||||
description: `Template imported from ${gitProvider === 'other' ? 'Git' : gitProvider.charAt(0).toUpperCase() + gitProvider.slice(1)}: ${gitUrl}`,
|
||||
type: 'custom',
|
||||
category: 'imported',
|
||||
is_custom: true,
|
||||
source: 'git',
|
||||
git_url: gitUrl.trim(),
|
||||
git_branch: gitBranch?.trim() || 'main',
|
||||
git_provider: gitProvider
|
||||
}
|
||||
} catch (attachErr) {
|
||||
console.error('[TemplateSelectionStep] attachRepository failed:', attachErr)
|
||||
// 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
|
||||
}
|
||||
} catch {}
|
||||
alert('Failed to attach repository. Please verify the URL/branch and your auth.');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[TemplateSelectionStep] Error importing from Git:', error)
|
||||
const errorMessage = error instanceof Error ? error.message : 'Failed to import from Git'
|
||||
alert(`Error: ${errorMessage}`)
|
||||
throw error as Error
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateTemplate = async (id: string, templateData: Partial<DatabaseTemplate>) => {
|
||||
try {
|
||||
// Find the template to determine if it's custom
|
||||
@ -819,7 +1069,10 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="group border-dashed border-2 border-white/15 bg-white/5 hover:border-white/25 transition-all cursor-pointer" onClick={() => user?.id ? setShowCustomForm(true) : window.location.href = '/signin'}>
|
||||
<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 }
|
||||
setShowCreateOptionDialog(true)
|
||||
}}>
|
||||
<CardContent className="text-center py-16 px-8 text-white/80">
|
||||
<div className="w-20 h-20 bg-white/10 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<Plus className="h-10 w-10 text-orange-400" />
|
||||
@ -833,7 +1086,11 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
: "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">
|
||||
<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 }
|
||||
setShowCreateOptionDialog(true)
|
||||
}}>
|
||||
{user?.id ? (
|
||||
<>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
@ -879,6 +1136,182 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Create Template Options Modal */}
|
||||
<Dialog open={showCreateOptionDialog} onOpenChange={(open) => {
|
||||
setShowCreateOptionDialog(open)
|
||||
if (!open) {
|
||||
setShowGitForm(false)
|
||||
setGitProvider('')
|
||||
setGitUrl('')
|
||||
setGitBranch('main')
|
||||
setGitAuthMethod('')
|
||||
setGitCredentials({ username: '', password: '', token: '', sshKey: '' })
|
||||
setGitStep('provider')
|
||||
setAuthUrl('')
|
||||
}
|
||||
}}>
|
||||
<DialogContent className="bg-white/10 border-white/20 text-white" showCloseButton>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Create Template</DialogTitle>
|
||||
<DialogDescription className="text-white/80">
|
||||
Choose how you want to create a template.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{!showGitForm ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<Button className="bg-orange-500 hover:bg-orange-400 text-black" onClick={() => {
|
||||
setShowCreateOptionDialog(false)
|
||||
setShowCustomForm(true)
|
||||
}}>Create Manually</Button>
|
||||
<Button variant="outline" className="border-white/20 text-white hover:bg-white/10" onClick={() => {
|
||||
setShowGitForm(true)
|
||||
setGitStep('provider')
|
||||
}}>
|
||||
Import from Git
|
||||
</Button>
|
||||
</div>
|
||||
) : gitStep === 'provider' ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-3">Select Git Provider</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.entries(gitProviders).map(([key, provider]) => (
|
||||
<Button
|
||||
key={key}
|
||||
variant="outline"
|
||||
className="border-white/20 text-white hover:bg-white/10 h-12 flex flex-col items-center gap-1"
|
||||
onClick={() => {
|
||||
setGitProvider(key)
|
||||
setGitStep('url')
|
||||
}}
|
||||
>
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d={provider.icon}/>
|
||||
</svg>
|
||||
{provider.name}
|
||||
</Button>
|
||||
))}
|
||||
</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={() => { setShowGitForm(false) }}>Back</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-white/70 hover:text-white p-1"
|
||||
onClick={() => setGitStep('provider')}
|
||||
>
|
||||
← Back to Provider
|
||||
</Button>
|
||||
<span className="text-sm text-white/70">
|
||||
Import from {gitProviders[gitProvider as keyof typeof gitProviders]?.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-1">Repository URL</label>
|
||||
<Input
|
||||
value={gitUrl}
|
||||
onChange={(e) => setGitUrl(e.target.value)}
|
||||
placeholder={gitProviders[gitProvider as keyof typeof gitProviders]?.placeholder}
|
||||
className="bg-white/10 border-white/20 text-white"
|
||||
/>
|
||||
<p className="text-xs text-white/50 mt-1">
|
||||
Enter the full URL to your {gitProviders[gitProvider as keyof typeof gitProviders]?.name} repository
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-1">Branch (optional)</label>
|
||||
<Input
|
||||
value={gitBranch}
|
||||
onChange={(e) => setGitBranch(e.target.value)}
|
||||
placeholder="main"
|
||||
className="bg-white/10 border-white/20 text-white"
|
||||
/>
|
||||
<p className="text-xs text-white/50 mt-1">
|
||||
Leave empty to use the default branch
|
||||
</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>
|
||||
<Button
|
||||
className="bg-orange-500 hover:bg-orange-400 text-black"
|
||||
type="button"
|
||||
onClick={(e) => { e.preventDefault(); handleCreateFromGit(); }}
|
||||
disabled={!gitUrl.trim() || (isGithubConnected === false && !authUrl)}
|
||||
>
|
||||
Import Template
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
//
|
||||
// export const BACKEND_URL = 'https://backend.codenuk.com';
|
||||
|
||||
export const BACKEND_URL = 'http://192.168.1.13:8000';
|
||||
export const BACKEND_URL = 'http://localhost:8000';
|
||||
|
||||
export const SOCKET_URL = BACKEND_URL;
|
||||
|
||||
|
||||
49
src/lib/api/github.ts
Normal file
49
src/lib/api/github.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { authApiClient } from '@/components/apis/authApiClients'
|
||||
|
||||
export interface AttachRepositoryPayload {
|
||||
repository_url: string
|
||||
branch_name?: string
|
||||
user_id?: string
|
||||
}
|
||||
|
||||
export interface AttachRepositoryResponse<T = unknown> {
|
||||
success: boolean
|
||||
message?: string
|
||||
data?: T
|
||||
requires_auth?: boolean
|
||||
auth_url?: string
|
||||
auth_error?: boolean
|
||||
}
|
||||
|
||||
export async function attachRepository(payload: AttachRepositoryPayload): Promise<AttachRepositoryResponse> {
|
||||
// Add user_id as query fallback besides header for gateway caching/proxies
|
||||
const rawUser = (typeof window !== 'undefined') ? localStorage.getItem('codenuk_user') : null
|
||||
const userId = rawUser ? (JSON.parse(rawUser)?.id || null) : null
|
||||
const url = userId ? `/api/github/attach-repository?user_id=${encodeURIComponent(userId)}` : '/api/github/attach-repository'
|
||||
const response = await authApiClient.post(url, { ...payload, user_id: userId || payload.user_id }, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
return response.data as AttachRepositoryResponse
|
||||
}
|
||||
|
||||
export interface GitHubAuthStatusData {
|
||||
connected: boolean
|
||||
github_username?: string
|
||||
github_user_id?: string
|
||||
connected_at?: string
|
||||
scopes?: string[]
|
||||
requires_auth?: boolean
|
||||
auth_url?: string
|
||||
}
|
||||
|
||||
export async function getGitHubAuthStatus(): Promise<AttachRepositoryResponse<GitHubAuthStatusData>> {
|
||||
const rawUser = (typeof window !== 'undefined') ? localStorage.getItem('codenuk_user') : null
|
||||
const userId = rawUser ? (JSON.parse(rawUser)?.id || null) : null
|
||||
const url = userId ? `/api/github/auth/github/status?user_id=${encodeURIComponent(userId)}` : '/api/github/auth/github/status'
|
||||
const response = await authApiClient.get(url)
|
||||
return response.data as AttachRepositoryResponse<GitHubAuthStatusData>
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user