'use client'
import { useEffect, useState, Suspense } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { supabase } from '@/lib/supabase'
import { IoLogoGithub } from 'react-icons/io5'
interface Repository {
id: number
name: string
full_name: string
private: boolean
updated_at: string
description?: string
}
function SelectRepoContent() {
const router = useRouter()
const searchParams = useSearchParams()
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 bookId = searchParams.get('bookId')
const accessToken = searchParams.get('accessToken')
const githubUsername = searchParams.get('githubUsername')
const redirectUrl = searchParams.get('redirectUrl') || '/dashboard'
const repoData = searchParams.get('repositories')
useEffect(() => {
if (!bookId || !accessToken || !repoData) {
router.push('/dashboard?error=Missing GitHub authorization data')
return
}
try {
const decodedRepos = JSON.parse(Buffer.from(repoData, 'base64').toString())
setRepositories(decodedRepos)
setFilteredRepos(decodedRepos)
} catch (error) {
console.error('Failed to decode repository data:', error)
router.push('/dashboard?error=Invalid repository data')
}
}, [bookId, accessToken, repoData, router])
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 (!bookId || !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! Redirect back to the book
const successUrl = new URL(redirectUrl, window.location.origin)
successUrl.searchParams.set('success', `Connected to ${repo.full_name}`)
router.push(successUrl.toString())
} catch (error) {
console.error('Error connecting repository:', error)
alert(error instanceof Error ? error.message : 'Failed to connect repository')
} finally {
setIsConnecting(false)
setSelectedRepo(null)
}
}
const handleCreateRepository = () => {
const createUrl = new URL('/auth/github/create-repo', window.location.origin)
createUrl.searchParams.set('bookId', bookId!)
createUrl.searchParams.set('accessToken', accessToken!)
createUrl.searchParams.set('githubUsername', githubUsername!)
createUrl.searchParams.set('redirectUrl', redirectUrl)
router.push(createUrl.toString())
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
if (!bookId || !accessToken) {
return (
<div className="min-h-screen flex items-center justify-center bg-slate-900">
<div className="text-center bg-slate-800 p-8 rounded-lg border border-slate-700">
<h1 className="text-2xl font-bold text-red-400 mb-2">Authorization Error</h1>
<p className="text-slate-400 mb-4">Missing required authorization data</p>
<button
onClick={() => router.push('/dashboard')}
className="bg-teal-600 text-white px-4 py-2 rounded-lg hover:bg-teal-700 transition-colors"
>
Return to Dashboard
</button>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-slate-900 py-8">
<div className="max-w-4xl mx-auto px-4">
<div className="text-center mb-8">
<div className="flex items-center justify-center gap-3 mb-4">
<IoLogoGithub className="w-8 h-8 text-slate-200" />
<h1 className="text-2xl font-semibold text-slate-200">
Connect GitHub Repository
</h1>
</div>
<p className="text-slate-400">
Choose a repository to connect with your book for version control
</p>
{githubUsername && (
<div className="mt-2 inline-block bg-slate-800 border border-slate-700 px-3 py-1 rounded-full text-sm text-slate-300">
Connected as @{githubUsername}
</div>
)}
</div>
{/* Search */}
<div className="mb-6">
<input
type="text"
placeholder="Search repositories..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-2 bg-slate-800 border border-slate-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-teal-500 text-slate-200 placeholder-slate-500"
/>
</div>
{/* Create New Repository Option */}
<div className="mb-6 p-6 border-2 border-dashed border-slate-700 rounded-lg hover:border-slate-600 transition-colors bg-slate-800/50">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="p-2 bg-teal-500/10 rounded-lg">
<svg className="h-5 w-5 text-teal-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
<div>
<h3 className="font-semibold text-slate-200">Create New Repository</h3>
<p className="text-sm text-slate-400">Start fresh with a new GitHub repository</p>
</div>
</div>
<button
onClick={handleCreateRepository}
className="px-4 py-2 bg-slate-700 hover:bg-slate-600 text-slate-200 rounded-lg transition-colors"
>
Create New
</button>
</div>
</div>
{/* Repository List */}
<div className="space-y-4">
{filteredRepos.length === 0 ? (
<div className="bg-slate-800/50 p-8 rounded-lg border border-slate-700 text-center">
<p className="text-slate-400">
{searchTerm ? 'No repositories match your search' : 'No repositories found'}
</p>
</div>
) : (
filteredRepos.map((repo) => (
<div key={repo.id} className="bg-slate-800/50 p-6 rounded-lg border border-slate-700 hover:border-slate-600 transition-colors">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2 mb-2">
<h3 className="text-lg font-semibold text-slate-200 truncate">
{repo.name}
</h3>
<span className={`inline-block px-2 py-1 text-xs rounded-full ${
repo.private
? 'bg-yellow-500/10 text-yellow-400'
: 'bg-green-500/10 text-green-400'
}`}>
{repo.private ? 'Private' : 'Public'}
</span>
</div>
{repo.description && (
<p className="text-slate-400 mb-2 text-sm">
{repo.description}
</p>
)}
<div className="flex items-center space-x-4 text-sm text-slate-500">
<span>{repo.full_name}</span>
<span>Updated {formatDate(repo.updated_at)}</span>
</div>
</div>
<button
onClick={() => handleConnectRepository(repo)}
disabled={isConnecting}
className={`ml-4 px-4 py-2 rounded-lg transition-colors ${
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 ? 'Connecting...' : 'Connect'}
</button>
</div>
</div>
))
)}
</div>
</div>
</div>
)
}
export default function SelectRepoPage() {
return (
<Suspense fallback={
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<h2 className="text-xl font-semibold text-gray-900 mb-2">Loading...</h2>
<p className="text-gray-600">Please wait...</p>
</div>
</div>
}>
<SelectRepoContent />
</Suspense>
)
}