'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { supabase } from '@/lib/supabase'
import { IoLogoGithub, IoSearchOutline, IoAddOutline, IoLockClosedOutline, IoGlobeOutline, IoCalendarOutline } from 'react-icons/io5'
import GitHubCreateRepo from './GitHubCreateRepo'
interface Repository {
id: number
name: string
full_name: string
private: boolean
updated_at: string
description?: string
}
interface GitHubRepoSelectorProps {
bookId: string
onConnect: () => void
}
export default function GitHubRepoSelector({ bookId, onConnect }: GitHubRepoSelectorProps) {
const router = useRouter()
const [repositories, setRepositories] = useState<Repository[]>([])
const [filteredRepos, setFilteredRepos] = useState<Repository[]>([])
const [searchTerm, setSearchTerm] = useState('')
const [isConnecting, setIsConnecting] = useState(false)
const [selectedRepo, setSelectedRepo] = useState<Repository | null>(null)
const [accessToken, setAccessToken] = useState<string>('')
const [githubUsername, setGithubUsername] = useState<string>('')
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [showCreateForm, setShowCreateForm] = useState(false)
useEffect(() => {
const loadRepositories = async () => {
try {
// First, check if we have OAuth data from URL parameters (after GitHub OAuth redirect)
const urlParams = new URLSearchParams(window.location.search)
const repoDataParam = urlParams.get('repositories')
const accessTokenParam = urlParams.get('accessToken')
const githubUsernameParam = urlParams.get('githubUsername')
if (repoDataParam && accessTokenParam && githubUsernameParam) {
// Use URL parameters (fresh OAuth data)
const decodedRepos = JSON.parse(Buffer.from(repoDataParam, 'base64').toString())
setRepositories(decodedRepos)
setFilteredRepos(decodedRepos)
setAccessToken(accessTokenParam)
setGithubUsername(githubUsernameParam)
// Clean up URL parameters
const cleanUrl = new URL(window.location.href)
cleanUrl.searchParams.delete('repositories')
cleanUrl.searchParams.delete('accessToken')
cleanUrl.searchParams.delete('githubUsername')
window.history.replaceState({}, '', cleanUrl.toString())
} else {
// Try to fetch from existing GitHub integration
const { data: { session } } = await supabase.auth.getSession()
const headers: Record<string, string> = {}
if (session?.access_token) {
headers['Authorization'] = `Bearer ${session.access_token}`
}
const response = await fetch('/api/github/repos', { headers })
if (response.ok) {
const data = await response.json()
setRepositories(data.repositories)
setFilteredRepos(data.repositories)
setAccessToken(data.accessToken)
setGithubUsername(data.githubUsername)
} else if (response.status === 404) {
// No GitHub integration found - this is expected for first-time users
setError(null)
} else {
// Other error
const errorData = await response.json()
setError(errorData.error || 'Failed to load repositories')
}
}
} catch (err) {
console.error('Failed to load repository data:', err)
setError('Failed to load repositories')
} finally {
setLoading(false)
}
}
loadRepositories()
}, [])
useEffect(() => {
const filtered = repositories.filter(repo =>
repo.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
repo.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
repo.full_name.toLowerCase().includes(searchTerm.toLowerCase())
)
setFilteredRepos(filtered)
}, [searchTerm, repositories])
const handleConnectRepository = async (repo: Repository) => {
if (!accessToken) return
setIsConnecting(true)
setSelectedRepo(repo)
try {
// Get current user
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
throw new Error('Please log in first')
}
// Create GitHub integration
const integration = {
provider: 'github',
repository_url: `https://github.com/${repo.full_name}`,
repository_name: repo.name,
repository_full_name: repo.full_name,
access_token: accessToken,
github_username: githubUsername,
repository_id: repo.id,
is_private: repo.private,
user_id: user.id
}
const response = await fetch(`/api/books/${bookId}/github-integration`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ integration }),
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to connect repository')
}
// Success! Call the onConnect callback
onConnect()
} catch (error) {
console.error('Error connecting repository:', error)
setError(error instanceof Error ? error.message : 'Failed to connect repository')
} finally {
setIsConnecting(false)
setSelectedRepo(null)
}
}
const handleShowCreateForm = () => {
setShowCreateForm(true)
}
const handleBackToRepoList = () => {
setShowCreateForm(false)
}
const handleRepoCreated = () => {
setShowCreateForm(false)
onConnect() // Refresh the integration status
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric'
})
}
if (loading) {
return (
<div className="p-4 text-center">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-teal-400 mx-auto mb-2"></div>
<p className="text-xs text-slate-400">Loading repositories...</p>
</div>
)
}
if (error) {
return (
<div className="p-4">
<div className="bg-red-900/20 border border-red-700 rounded-lg p-3 text-red-300 text-sm">
{error}
</div>
</div>
)
}
if (repositories.length === 0) {
return (
<div className="p-4 text-center">
<IoLogoGithub className="w-8 h-8 text-slate-400 mx-auto mb-2" />
<p className="text-sm text-slate-400 mb-2">No repositories found</p>
<button
onClick={handleShowCreateForm}
className="text-xs bg-teal-600 hover:bg-teal-700 text-white px-3 py-1.5 rounded-md transition-colors"
>
Create Repository
</button>
</div>
)
}
// Show create form if requested
if (showCreateForm && accessToken && githubUsername) {
return (
<GitHubCreateRepo
bookId={bookId}
accessToken={accessToken}
githubUsername={githubUsername}
onBack={handleBackToRepoList}
onCreate={handleRepoCreated}
/>
)
}
return (
<div className="flex flex-col h-full">
{/* Header with GitHub username */}
<div className="p-3 border-b border-slate-700">
<div className="flex items-center gap-2 mb-2">
<IoLogoGithub className="w-4 h-4 text-green-400" />
<span className="text-sm font-semibold text-slate-200">Select Repository</span>
</div>
<div className="text-xs bg-green-900/20 text-green-400 px-2 py-1 rounded-md inline-block">
@{githubUsername}
</div>
</div>
{/* Search */}
<div className="p-3 border-b border-slate-700">
<div className="relative">
<IoSearchOutline className="absolute left-3 top-1/2 transform -translate-y-1/2 w-3 h-3 text-slate-500" />
<input
type="text"
placeholder="Search repositories..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-8 pr-3 py-2 text-xs bg-slate-800 border border-slate-700 rounded-md focus:outline-none focus:ring-1 focus:ring-teal-500 text-slate-200 placeholder-slate-500"
/>
</div>
</div>
{/* Create New Repository Option */}
<div className="p-3 border-b border-slate-700">
<button
onClick={handleShowCreateForm}
className="w-full p-2 border-2 border-dashed border-slate-700 rounded-md hover:border-slate-600 transition-colors text-left group"
>
<div className="flex items-center gap-2">
<div className="p-1 bg-teal-500/10 rounded group-hover:bg-teal-500/20 transition-colors">
<IoAddOutline className="w-3 h-3 text-teal-400" />
</div>
<div>
<div className="text-xs font-medium text-slate-200">Create New Repository</div>
<div className="text-xs text-slate-400">Start fresh on GitHub</div>
</div>
</div>
</button>
</div>
{/* Repository List */}
<div className="flex-1 overflow-y-auto">
{filteredRepos.length === 0 ? (
<div className="p-4 text-center">
<p className="text-xs text-slate-400">
{searchTerm ? 'No repositories match your search' : 'No repositories found'}
</p>
</div>
) : (
<div className="space-y-1 p-2">
{filteredRepos.map((repo) => (
<div
key={repo.id}
className="p-2 rounded-md border border-slate-700 hover:border-slate-600 hover:bg-slate-800/30 transition-colors group"
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5 mb-1">
<div className="text-sm font-medium text-slate-200 truncate">
{repo.name}
</div>
{repo.private ? (
<IoLockClosedOutline className="w-3 h-3 text-yellow-400 flex-shrink-0" />
) : (
<IoGlobeOutline className="w-3 h-3 text-green-400 flex-shrink-0" />
)}
</div>
{repo.description && (
<p className="text-xs text-slate-400 mb-1 line-clamp-2">
{repo.description}
</p>
)}
<div className="flex items-center gap-2 text-xs text-slate-500">
<IoCalendarOutline className="w-3 h-3" />
<span>{formatDate(repo.updated_at)}</span>
</div>
</div>
<button
onClick={() => handleConnectRepository(repo)}
disabled={isConnecting}
className={`px-2 py-1 text-xs rounded-md font-medium transition-colors flex-shrink-0 ${
isConnecting && selectedRepo?.id === repo.id
? 'bg-slate-700 text-slate-400 cursor-not-allowed'
: 'bg-teal-600 text-white hover:bg-teal-700'
}`}
>
{isConnecting && selectedRepo?.id === repo.id ? (
<div className="flex items-center gap-1">
<div className="animate-spin rounded-full h-3 w-3 border border-slate-400 border-t-transparent"></div>
<span>Connecting...</span>
</div>
) : (
'Connect'
)}
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}