bookwiz.io / app / auth / github / select-repo / page.tsx
page.tsx
Raw
'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>
  )
}