import React, { useState, useEffect, useMemo } from 'react'; import { motion } from 'framer-motion'; import { Button } from '~/components/ui/Button'; import { BranchSelector } from '~/components/ui/BranchSelector'; import { GitHubRepositoryCard } from './GitHubRepositoryCard'; import type { GitHubRepoInfo } from '~/types/GitHub'; import { useGitHubConnection, useGitHubStats } from '~/lib/hooks'; import { classNames } from '~/utils/classNames'; import { Search, RefreshCw, GitBranch, Calendar, Filter } from 'lucide-react'; interface GitHubRepositorySelectorProps { onClone?: (repoUrl: string, branch?: string) => void; className?: string; } type SortOption = 'updated' | 'stars' | 'name' | 'created'; type FilterOption = 'all' | 'own' | 'forks' | 'archived'; export function GitHubRepositorySelector({ onClone, className }: GitHubRepositorySelectorProps) { const { connection, isConnected } = useGitHubConnection(); const { stats, isLoading: isStatsLoading, refreshStats, } = useGitHubStats(connection, { autoFetch: true, cacheTimeout: 30 * 60 * 1000, // 30 minutes }); const [searchQuery, setSearchQuery] = useState(''); const [sortBy, setSortBy] = useState('updated'); const [filterBy, setFilterBy] = useState('all'); const [currentPage, setCurrentPage] = useState(1); const [selectedRepo, setSelectedRepo] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); const [isBranchSelectorOpen, setIsBranchSelectorOpen] = useState(false); const [error, setError] = useState(null); const repositories = stats?.repos || []; const REPOS_PER_PAGE = 12; // Filter and search repositories const filteredRepositories = useMemo(() => { if (!repositories) { return []; } const filtered = repositories.filter((repo: GitHubRepoInfo) => { // Search filter const matchesSearch = !searchQuery || repo.name.toLowerCase().includes(searchQuery.toLowerCase()) || repo.description?.toLowerCase().includes(searchQuery.toLowerCase()) || repo.full_name.toLowerCase().includes(searchQuery.toLowerCase()); // Type filter let matchesFilter = true; switch (filterBy) { case 'own': matchesFilter = !repo.fork; break; case 'forks': matchesFilter = repo.fork === true; break; case 'archived': matchesFilter = repo.archived === true; break; case 'all': default: matchesFilter = true; break; } return matchesSearch && matchesFilter; }); // Sort repositories filtered.sort((a: GitHubRepoInfo, b: GitHubRepoInfo) => { switch (sortBy) { case 'name': return a.name.localeCompare(b.name); case 'stars': return b.stargazers_count - a.stargazers_count; case 'created': return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(); // Using updated_at as proxy case 'updated': default: return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(); } }); return filtered; }, [repositories, searchQuery, sortBy, filterBy]); // Pagination const totalPages = Math.ceil(filteredRepositories.length / REPOS_PER_PAGE); const startIndex = (currentPage - 1) * REPOS_PER_PAGE; const currentRepositories = filteredRepositories.slice(startIndex, startIndex + REPOS_PER_PAGE); const handleRefresh = async () => { setIsRefreshing(true); setError(null); try { await refreshStats(); } catch (err) { console.error('Failed to refresh GitHub repositories:', err); setError(err instanceof Error ? err.message : 'Failed to refresh repositories'); } finally { setIsRefreshing(false); } }; const handleCloneRepository = (repo: GitHubRepoInfo) => { setSelectedRepo(repo); setIsBranchSelectorOpen(true); }; const handleBranchSelect = (branch: string) => { if (onClone && selectedRepo) { const cloneUrl = selectedRepo.html_url + '.git'; onClone(cloneUrl, branch); } setSelectedRepo(null); }; const handleCloseBranchSelector = () => { setIsBranchSelectorOpen(false); setSelectedRepo(null); }; // Reset to first page when filters change useEffect(() => { setCurrentPage(1); }, [searchQuery, sortBy, filterBy]); if (!isConnected || !connection) { return (

Please connect to GitHub first to browse repositories

); } if (isStatsLoading && !stats) { return (

Loading repositories...

); } if (!repositories.length) { return (

No repositories found

); } return ( {/* Header with stats */}

Select Repository to Clone

{filteredRepositories.length} of {repositories.length} repositories

{error && repositories.length > 0 && (

Warning: {error}. Showing cached data.

)} {/* Search and Filters */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary focus:outline-none focus:ring-1 focus:ring-bolt-elements-borderColorActive" />
{/* Sort */}
{/* Filter */}
{/* Repository Grid */} {currentRepositories.length > 0 ? ( <>
{currentRepositories.map((repo) => ( handleCloneRepository(repo)} /> ))}
{/* Pagination */} {totalPages > 1 && (
Showing {Math.min(startIndex + 1, filteredRepositories.length)} to{' '} {Math.min(startIndex + REPOS_PER_PAGE, filteredRepositories.length)} of {filteredRepositories.length}{' '} repositories
{currentPage} of {totalPages}
)} ) : (

No repositories found matching your search criteria.

)} {/* Branch Selector Modal */} {selectedRepo && ( )}
); }