import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import { toast } from 'react-toastify'; import { classNames } from '~/utils/classNames'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '~/components/ui/Collapsible'; import { Button } from '~/components/ui/Button'; import { useGitLabConnection } from '~/lib/stores/gitlabConnection'; import { RepositoryList } from './RepositoryList'; import { StatsDisplay } from './StatsDisplay'; import type { GitLabProjectInfo } from '~/types/GitLab'; interface GitLabConnectionProps { onCloneRepository?: (repoUrl: string) => void; } export default function GitLabConnection({ onCloneRepository }: GitLabConnectionProps = {}) { const { connection: connectionAtom, isConnected, user: userAtom, stats, gitlabUrl: gitlabUrlAtom, connect, disconnect, fetchStats, loadSavedConnection, setGitLabUrl, setToken, autoConnect, } = useGitLabConnection(); const [isLoading, setIsLoading] = useState(true); const [isConnecting, setIsConnecting] = useState(false); const [isFetchingStats, setIsFetchingStats] = useState(false); const [isStatsExpanded, setIsStatsExpanded] = useState(false); useEffect(() => { const initializeConnection = async () => { setIsLoading(true); const saved = loadSavedConnection(); if (saved?.user && saved?.token) { // If we have stats, no need to fetch them again if (!saved.stats || !saved.stats.projects || saved.stats.projects.length === 0) { await fetchStats(); } } else if (import.meta.env?.VITE_GITLAB_ACCESS_TOKEN) { // Auto-connect using environment variable if no saved connection const result = await autoConnect(); if (result.success) { toast.success('Connected to GitLab automatically'); } } setIsLoading(false); }; initializeConnection(); }, [autoConnect, fetchStats, loadSavedConnection]); const handleConnect = async (event: React.FormEvent) => { event.preventDefault(); setIsConnecting(true); try { const result = await connect(connectionAtom.get().token, gitlabUrlAtom.get()); if (result.success) { toast.success('Connected to GitLab successfully'); await fetchStats(); } else { toast.error(`Failed to connect to GitLab: ${result.error}`); } } catch (error) { console.error('Failed to connect to GitLab:', error); toast.error(`Failed to connect to GitLab: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { setIsConnecting(false); } }; const handleDisconnect = () => { disconnect(); toast.success('Disconnected from GitLab'); }; const handleCloneRepository = (repoUrl: string) => { if (onCloneRepository) { onCloneRepository(repoUrl); } else { window.open(repoUrl, '_blank'); } }; if (isLoading || isConnecting || isFetchingStats) { return (
Loading...
); } return (

GitLab Connection

{!isConnected && (

Tip: You can also set the{' '} VITE_GITLAB_ACCESS_TOKEN{' '} environment variable to connect automatically.

For self-hosted GitLab instances, also set{' '} VITE_GITLAB_URL=https://your-gitlab-instance.com

)}
setGitLabUrl(e.target.value)} disabled={isConnecting || isConnected.get()} placeholder="https://gitlab.com" 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-bolt-elements-borderColorActive', 'disabled:opacity-50', )} />
setToken(e.target.value)} disabled={isConnecting || isConnected.get()} placeholder="Enter your GitLab access 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-bolt-elements-borderColorActive', 'disabled:opacity-50', )} />
Get your token
Required scopes: api, read_repository
{!isConnected ? ( ) : ( <>
Connected to GitLab
)}
{isConnected.get() && userAtom.get() && stats.get() && (
{userAtom.get()?.avatar_url && userAtom.get()?.avatar_url !== 'null' && userAtom.get()?.avatar_url !== '' ? ( {userAtom.get()?.username} { // Fallback to initials if avatar fails to load const target = e.target as HTMLImageElement; target.style.display = 'none'; const parent = target.parentElement; if (parent) { const user = userAtom.get(); parent.innerHTML = (user?.name || user?.username || 'U').charAt(0).toUpperCase(); parent.classList.add( 'text-white', 'font-semibold', 'text-sm', 'flex', 'items-center', 'justify-center', ); } }} /> ) : (
{(userAtom.get()?.name || userAtom.get()?.username || 'U').charAt(0).toUpperCase()}
)}

{userAtom.get()?.name || userAtom.get()?.username}

{userAtom.get()?.username}

GitLab Stats
{ const result = await fetchStats(); if (result.success) { toast.success('Stats refreshed'); } else { toast.error(`Failed to refresh stats: ${result.error}`); } }} isRefreshing={isFetchingStats} /> handleCloneRepository(repo.http_url_to_repo)} onRefresh={async () => { const result = await fetchStats(true); // Force refresh if (result.success) { toast.success('Repositories refreshed'); } else { toast.error(`Failed to refresh repositories: ${result.error}`); } }} isRefreshing={isFetchingStats} />
)}
); }