import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import { toast } from 'react-toastify'; import { logStore } from '~/lib/stores/logs'; import { classNames } from '~/utils/classNames'; import Cookies from 'js-cookie'; interface GitHubUserResponse { login: string; avatar_url: string; html_url: string; name: string; bio: string; public_repos: number; followers: number; following: number; created_at: string; public_gists: number; } interface GitHubRepoInfo { name: string; full_name: string; html_url: string; description: string; stargazers_count: number; forks_count: number; default_branch: string; updated_at: string; languages_url: string; } interface GitHubOrganization { login: string; avatar_url: string; html_url: string; } interface GitHubEvent { id: string; type: string; repo: { name: string; }; created_at: string; } interface GitHubLanguageStats { [language: string]: number; } interface GitHubStats { repos: GitHubRepoInfo[]; totalStars: number; totalForks: number; organizations: GitHubOrganization[]; recentActivity: GitHubEvent[]; languages: GitHubLanguageStats; totalGists: number; } interface GitHubConnection { user: GitHubUserResponse | null; token: string; tokenType: 'classic' | 'fine-grained'; stats?: GitHubStats; } export function GithubConnection() { const [connection, setConnection] = useState({ user: null, token: '', tokenType: 'classic', }); const [isLoading, setIsLoading] = useState(true); const [isConnecting, setIsConnecting] = useState(false); const [isFetchingStats, setIsFetchingStats] = useState(false); const [isStatsExpanded, setIsStatsExpanded] = useState(false); const fetchGitHubStats = async (token: string) => { try { setIsFetchingStats(true); const reposResponse = await fetch( 'https://api.github.com/user/repos?sort=updated&per_page=10&affiliation=owner,organization_member,collaborator', { headers: { Authorization: `Bearer ${token}`, }, }, ); if (!reposResponse.ok) { throw new Error('Failed to fetch repositories'); } const repos = (await reposResponse.json()) as GitHubRepoInfo[]; const orgsResponse = await fetch('https://api.github.com/user/orgs', { headers: { Authorization: `Bearer ${token}`, }, }); if (!orgsResponse.ok) { throw new Error('Failed to fetch organizations'); } const organizations = (await orgsResponse.json()) as GitHubOrganization[]; const eventsResponse = await fetch('https://api.github.com/users/' + connection.user?.login + '/events/public', { headers: { Authorization: `Bearer ${token}`, }, }); if (!eventsResponse.ok) { throw new Error('Failed to fetch events'); } const recentActivity = ((await eventsResponse.json()) as GitHubEvent[]).slice(0, 5); const languagePromises = repos.map((repo) => fetch(repo.languages_url, { headers: { Authorization: `Bearer ${token}`, }, }).then((res) => res.json() as Promise>), ); const repoLanguages = await Promise.all(languagePromises); const languages: GitHubLanguageStats = {}; repoLanguages.forEach((repoLang) => { Object.entries(repoLang).forEach(([lang, bytes]) => { languages[lang] = (languages[lang] || 0) + bytes; }); }); const totalStars = repos.reduce((acc, repo) => acc + repo.stargazers_count, 0); const totalForks = repos.reduce((acc, repo) => acc + repo.forks_count, 0); const totalGists = connection.user?.public_gists || 0; setConnection((prev) => ({ ...prev, stats: { repos, totalStars, totalForks, organizations, recentActivity, languages, totalGists, }, })); } catch (error) { logStore.logError('Failed to fetch GitHub stats', { error }); toast.error('Failed to fetch GitHub statistics'); } finally { setIsFetchingStats(false); } }; useEffect(() => { const savedConnection = localStorage.getItem('github_connection'); if (savedConnection) { const parsed = JSON.parse(savedConnection); if (!parsed.tokenType) { parsed.tokenType = 'classic'; } setConnection(parsed); if (parsed.user && parsed.token) { fetchGitHubStats(parsed.token); } } else if (import.meta.env.VITE_GITHUB_ACCESS_TOKEN) { fetchGithubUser(import.meta.env.VITE_GITHUB_ACCESS_TOKEN); } setIsLoading(false); }, []); if (isLoading || isConnecting || isFetchingStats) { return ; } const fetchGithubUser = async (token: string) => { try { setIsConnecting(true); const response = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Invalid token or unauthorized'); } const data = (await response.json()) as GitHubUserResponse; const newConnection: GitHubConnection = { user: data, token, tokenType: connection.tokenType, }; localStorage.setItem('github_connection', JSON.stringify(newConnection)); Cookies.set('githubToken', token); Cookies.set('githubUsername', data.login); Cookies.set('git:github.com', JSON.stringify({ username: token, password: 'x-oauth-basic' })); setConnection(newConnection); await fetchGitHubStats(token); toast.success('Successfully connected to GitHub'); } catch (error) { logStore.logError('Failed to authenticate with GitHub', { error }); toast.error('Failed to connect to GitHub'); setConnection({ user: null, token: '', tokenType: 'classic' }); } finally { setIsConnecting(false); } }; const handleConnect = async (event: React.FormEvent) => { event.preventDefault(); await fetchGithubUser(connection.token); }; const handleDisconnect = () => { localStorage.removeItem('github_connection'); setConnection({ user: null, token: '', tokenType: 'classic' }); toast.success('Disconnected from GitHub'); }; return (

GitHub Connection

setConnection((prev) => ({ ...prev, token: e.target.value }))} disabled={isConnecting || !!connection.user} placeholder={`Enter your GitHub ${ connection.tokenType === 'classic' ? 'personal access token' : 'fine-grained token' }`} className={classNames( 'w-full px-3 py-2 rounded-lg text-sm', 'bg-[#F8F8F8] dark:bg-[#1A1A1A]', 'border border-[#E5E5E5] dark:border-[#333333]', 'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary', 'focus:outline-none focus:ring-1 focus:ring-purple-500', 'disabled:opacity-50', )} />
Get your token
Required scopes:{' '} {connection.tokenType === 'classic' ? 'repo, read:org, read:user' : 'Repository access, Organization access'}
{!connection.user ? ( ) : ( )} {connection.user && (
Connected to GitHub )}
{connection.user && connection.stats && (
{isStatsExpanded && (
{connection.stats.organizations.length > 0 && (

Organizations

{connection.stats.organizations.map((org) => ( {org.login} {org.login} ))}
)} {/* Languages Section */}

Top Languages

{Object.entries(connection.stats.languages) .sort(([, a], [, b]) => b - a) .slice(0, 5) .map(([language]) => ( {language} ))}
{/* Recent Activity Section */}

Recent Activity

{connection.stats.recentActivity.map((event) => (
{event.type.replace('Event', '')} on {event.repo.name}
{new Date(event.created_at).toLocaleDateString()} at{' '} {new Date(event.created_at).toLocaleTimeString()}
))}
{/* Additional Stats */}
Member Since
{new Date(connection.user.created_at).toLocaleDateString()}
Public Gists
{connection.stats.totalGists}
Organizations
{connection.stats.organizations.length}
Languages
{Object.keys(connection.stats.languages).length}
{/* Repositories Section */}

Recent Repositories

); } function LoadingSpinner() { return (
Loading...
); }