bookwiz.io / components / GitHubCreateRepo.tsx
GitHubCreateRepo.tsx
Raw
'use client'

import { useState } from 'react'
import { supabase } from '@/lib/supabase'
import { IoArrowBackOutline, IoLogoGithub, IoLockClosedOutline, IoGlobeOutline } from 'react-icons/io5'

interface GitHubCreateRepoProps {
  bookId: string
  accessToken: string
  githubUsername: string
  onBack: () => void
  onCreate: () => void
}

export default function GitHubCreateRepo({ 
  bookId, 
  accessToken, 
  githubUsername, 
  onBack, 
  onCreate 
}: GitHubCreateRepoProps) {
  const [repoName, setRepoName] = useState('')
  const [description, setDescription] = useState('')
  const [isPrivate, setIsPrivate] = useState(true)
  const [isCreating, setIsCreating] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const handleCreateRepository = async (e: React.FormEvent) => {
    e.preventDefault()
    
    if (!repoName.trim() || !accessToken || !bookId) return
    
    setIsCreating(true)
    setError(null)

    try {
      // Create repository via GitHub API
      const response = await fetch('https://api.github.com/user/repos', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Accept': 'application/vnd.github.v3+json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          name: repoName,
          description: description || undefined,
          private: isPrivate,
          auto_init: true // Initialize with README to avoid empty repo issues
        }),
      })

      if (!response.ok) {
        const errorData = await response.json()
        throw new Error(errorData.message || 'Failed to create repository')
      }

      const newRepo = await response.json()

      // 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: newRepo.html_url,
        repository_name: newRepo.name,
        repository_full_name: newRepo.full_name,
        access_token: accessToken,
        github_username: githubUsername,
        repository_id: newRepo.id,
        is_private: newRepo.private,
        user_id: user.id
      }

      const integrationResponse = await fetch(`/api/books/${bookId}/github-integration`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ integration }),
      })

      if (!integrationResponse.ok) {
        const errorData = await integrationResponse.json()
        throw new Error(errorData.error || 'Failed to connect repository')
      }

      // Success! Call the onCreate callback
      onCreate()

    } catch (error) {
      setError(error instanceof Error ? error.message : 'Failed to create repository')
    } finally {
      setIsCreating(false)
    }
  }

  return (
    <div className="flex flex-col h-full">
      {/* Header */}
      <div className="p-3 border-b border-slate-700">
        <div className="flex items-center gap-2 mb-2">
          <button
            onClick={onBack}
            className="p-1 rounded hover:bg-slate-700 text-slate-400 hover:text-slate-200 transition-colors"
          >
            <IoArrowBackOutline className="w-4 h-4" />
          </button>
          <IoLogoGithub className="w-4 h-4 text-green-400" />
          <span className="text-sm font-semibold text-slate-200">Create 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>

      {/* Form */}
      <div className="flex-1 overflow-y-auto p-3">
        <form onSubmit={handleCreateRepository} className="space-y-4">
          {/* Repository Name */}
          <div>
            <label htmlFor="repoName" className="block text-xs font-medium text-slate-300 mb-1">
              Repository Name *
            </label>
            <input
              type="text"
              id="repoName"
              value={repoName}
              onChange={(e) => setRepoName(e.target.value)}
              placeholder="my-awesome-book"
              required
              className="w-full px-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"
            />
            <p className="text-xs text-slate-500 mt-1">
              Lowercase letters, numbers, and hyphens only
            </p>
          </div>

          {/* Description */}
          <div>
            <label htmlFor="description" className="block text-xs font-medium text-slate-300 mb-1">
              Description (Optional)
            </label>
            <textarea
              id="description"
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              placeholder="A brief description..."
              rows={2}
              className="w-full px-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 resize-none"
            />
          </div>

          {/* Visibility */}
          <div>
            <label className="block text-xs font-medium text-slate-300 mb-2">
              Visibility
            </label>
            <div className="space-y-2">
              <label className="flex items-center">
                <input
                  type="radio"
                  value="private"
                  checked={isPrivate}
                  onChange={() => setIsPrivate(true)}
                  className="mr-2 accent-teal-500"
                />
                <IoLockClosedOutline className="w-3 h-3 text-yellow-400 mr-1" />
                <div className="flex-1">
                  <div className="text-xs font-medium text-slate-200">Private</div>
                  <div className="text-xs text-slate-400">Only you can see this</div>
                </div>
              </label>
              <label className="flex items-center">
                <input
                  type="radio"
                  value="public"
                  checked={!isPrivate}
                  onChange={() => setIsPrivate(false)}
                  className="mr-2 accent-teal-500"
                />
                <IoGlobeOutline className="w-3 h-3 text-green-400 mr-1" />
                <div className="flex-1">
                  <div className="text-xs font-medium text-slate-200">Public</div>
                  <div className="text-xs text-slate-400">Anyone can see this</div>
                </div>
              </label>
            </div>
          </div>

          {/* Error Display */}
          {error && (
            <div className="bg-red-900/20 border border-red-700 rounded-md p-2">
              <p className="text-xs text-red-300">{error}</p>
            </div>
          )}

          {/* Submit Button */}
          <button
            type="submit"
            disabled={!repoName.trim() || isCreating}
            className={`w-full px-3 py-2 text-xs rounded-md font-medium transition-colors ${
              !repoName.trim() || isCreating
                ? 'bg-slate-700 text-slate-400 cursor-not-allowed'
                : 'bg-teal-600 text-white hover:bg-teal-700'
            }`}
          >
            {isCreating ? (
              <div className="flex items-center justify-center gap-2">
                <div className="animate-spin rounded-full h-3 w-3 border border-slate-400 border-t-transparent"></div>
                <span>Creating...</span>
              </div>
            ) : (
              'Create Repository'
            )}
          </button>
        </form>
      </div>
    </div>
  )
}