Files
bolt-diy/app/components/@settings/tabs/connections/github/GitHubConnection.tsx
Stijnus 3ea96506ea feat: gitLab Integration Implementation / github refactor / overal improvements (#1963)
* Add GitLab integration components

Introduced PushToGitLabDialog and GitlabConnection components to handle GitLab project connections and push functionality. Includes user authentication, project handling, and UI for seamless integration with GitLab.

* Add components for GitLab connection and push dialog

Introduce `GitlabConnection` and `PushToGitLabDialog` components to handle GitLab integration. These components allow users to connect their GitLab account, manage recent projects, and push code to a GitLab repository with detailed configurations and feedback.

* Fix GitLab personal access tokens link to use correct URL

* Update GitHub push call to use new pushToRepository method

* Enhance GitLab integration with performance improvements

- Add comprehensive caching system for repositories and user data
- Implement pagination and search/filter functionality with debouncing
- Add skeleton loaders and improved loading states
- Implement retry logic for API calls with exponential backoff
- Add background refresh capabilities
- Improve error handling and user feedback
- Optimize API calls to reduce loading times

* feat: implement GitLab integration with connection management and repository handling

- Add GitLab connection UI components
- Implement GitLab API service for repository operations
- Add GitLab connection store for state management
- Update existing connection components (Vercel, Netlify)
- Add repository listing and statistics display
- Refactor GitLab components into organized folder structure

* fix: resolve GitLab deployment issues and improve user experience

- Fix DialogTitle accessibility warnings for screen readers
- Remove CORS-problematic attributes from avatar images to prevent loading errors
- Enhance GitLab API error handling with detailed error messages
- Fix project creation settings to prevent initial commit conflicts
- Add automatic GitLab connection state initialization on app startup
- Improve deployment dialog UI with better error handling and user feedback
- Add GitLab deployment source type to action runner system
- Clean up deprecated push dialog files and consolidate deployment components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: implement GitHub clone repository dialog functionality

This commit fixes the missing GitHub repository selection dialog in the "Clone a repo" feature
by implementing the same elegant interface pattern used by GitLab.

Key Changes:
- Added onCloneRepository prop support to GitHubConnection component
- Updated RepositoryCard to generate proper GitHub clone URLs (https://github.com/{full_name}.git)
- Implemented full GitHub repository selection dialog in GitCloneButton.tsx
- Added proper dialog close handling after successful clone operations
- Maintained existing GitHub connection settings page functionality

Technical Details:
- Follows same component patterns as GitLab implementation
- Uses proper TypeScript interfaces for clone URL handling
- Includes professional dialog styling with loading states
- Supports repository search, pagination, and authentication flow

The GitHub clone experience now matches GitLab's functionality, providing users with
a unified and intuitive repository selection interface across both providers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Clean up unused connection components

- Remove ConnectionForm.tsx (unused GitHub form component)
- Remove CreateBranchDialog.tsx (unused branch creation dialog)
- Remove RepositoryDialogContext.tsx (unused context provider)
- Remove empty components/ directory

These files were not referenced anywhere in the codebase and were leftover from development.

* Remove environment variables info section from ConnectionsTab

- Remove collapsible environment variables section
- Clean up unused state and imports
- Simplify the connections tab UI

* Reorganize connections folder structure

- Create netlify/ folder and move NetlifyConnection.tsx
- Create vercel/ folder and move VercelConnection.tsx
- Add index.ts files for both netlify and vercel folders
- Update imports in ConnectionsTab.tsx to use new folder structure
- All connection components now follow consistent folder organization

---------

Co-authored-by: Hayat Bourgi <hayat.bourgi@montyholding.com>
Co-authored-by: Hayat55 <53140162+Hayat55@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-05 14:01:33 +02:00

277 lines
11 KiB
TypeScript

import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { toast } from 'react-toastify';
import { useStore } from '@nanostores/react';
import { classNames } from '~/utils/classNames';
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '~/components/ui/Collapsible';
import { Button } from '~/components/ui/Button';
import {
githubConnectionAtom,
githubConnectionStore,
isGitHubConnected,
isGitHubConnecting,
isGitHubLoadingStats,
} from '~/lib/stores/githubConnection';
import { AuthDialog } from './AuthDialog';
import { StatsDisplay } from './StatsDisplay';
import { RepositoryList } from './RepositoryList';
interface GitHubConnectionProps {
onCloneRepository?: (repoUrl: string) => void;
}
export default function GitHubConnection({ onCloneRepository }: GitHubConnectionProps = {}) {
const connection = useStore(githubConnectionAtom);
const isConnected = useStore(isGitHubConnected);
const isConnecting = useStore(isGitHubConnecting);
const isLoadingStats = useStore(isGitHubLoadingStats);
const [isAuthDialogOpen, setIsAuthDialogOpen] = useState(false);
const [isStatsExpanded, setIsStatsExpanded] = useState(false);
const [isReposExpanded, setIsReposExpanded] = useState(false);
const handleConnect = () => {
setIsAuthDialogOpen(true);
};
const handleDisconnect = () => {
githubConnectionStore.disconnect();
setIsStatsExpanded(false);
setIsReposExpanded(false);
toast.success('Disconnected from GitHub');
};
const handleRefreshStats = async () => {
try {
await githubConnectionStore.fetchStats();
toast.success('GitHub stats refreshed');
} catch (error) {
toast.error(`Failed to refresh stats: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
};
const handleTokenTypeChange = (tokenType: 'classic' | 'fine-grained') => {
githubConnectionStore.updateTokenType(tokenType);
};
const handleCloneRepository = (repoUrl: string) => {
if (onCloneRepository) {
onCloneRepository(repoUrl);
} else {
window.open(repoUrl, '_blank');
}
};
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor">
<div className="i-ph:git-repository text-bolt-elements-icon-primary w-5 h-5" />
</div>
<div>
<h3 className="text-lg font-semibold text-bolt-elements-textPrimary">GitHub</h3>
<p className="text-sm text-bolt-elements-textSecondary">
{isConnected
? `Connected as ${connection.user?.login}`
: 'Connect your GitHub account to manage repositories'}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{isConnected ? (
<>
<Button
onClick={handleRefreshStats}
disabled={isLoadingStats}
variant="outline"
size="sm"
className="flex items-center gap-2"
>
{isLoadingStats ? (
<>
<div className="i-ph:spinner-gap w-4 h-4 animate-spin" />
Refreshing...
</>
) : (
<>
<div className="i-ph:arrows-clockwise w-4 h-4" />
Refresh Stats
</>
)}
</Button>
<Button
onClick={handleDisconnect}
variant="outline"
size="sm"
className="text-bolt-elements-textDanger hover:text-bolt-elements-textDanger"
>
<div className="i-ph:sign-out w-4 h-4 mr-2" />
Disconnect
</Button>
</>
) : (
<Button
onClick={handleConnect}
disabled={isConnecting}
variant="outline"
size="sm"
className="flex items-center gap-2"
>
{isConnecting ? (
<>
<div className="i-ph:spinner-gap w-4 h-4 animate-spin" />
Connecting...
</>
) : (
<>
<div className="i-ph:plus w-4 h-4" />
Connect
</>
)}
</Button>
)}
</div>
</div>
{/* Connection Status */}
<div className="p-4 rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor">
<div className="flex items-center gap-3">
<div
className={classNames(
'w-3 h-3 rounded-full',
isConnected ? 'bg-bolt-elements-icon-success' : 'bg-bolt-elements-icon-secondary',
)}
/>
<span className="text-sm font-medium text-bolt-elements-textPrimary">
{isConnected ? 'Connected' : 'Not Connected'}
</span>
{connection.rateLimit && (
<span className="text-xs text-bolt-elements-textSecondary ml-auto">
Rate limit: {connection.rateLimit.remaining}/{connection.rateLimit.limit}
</span>
)}
</div>
{/* Token Type Selection */}
{isConnected && (
<div className="mt-3 pt-3 border-t border-bolt-elements-borderColor">
<label className="block text-xs font-medium text-bolt-elements-textPrimary mb-2">Token Type</label>
<div className="flex gap-3">
{(['classic', 'fine-grained'] as const).map((type) => (
<label key={type} className="flex items-center cursor-pointer">
<input
type="radio"
value={type}
checked={connection.tokenType === type}
onChange={() => handleTokenTypeChange(type)}
className="mr-2 text-bolt-elements-item-contentAccent focus:ring-bolt-elements-item-contentAccent"
/>
<span className="text-xs text-bolt-elements-textSecondary capitalize">
{type.replace('-', ' ')} Token
</span>
</label>
))}
</div>
</div>
)}
</div>
{/* User Profile */}
{isConnected && connection.user && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="p-4 rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor"
>
<div className="flex items-center gap-4">
<img
src={connection.user.avatar_url}
alt={connection.user.login}
className="w-12 h-12 rounded-full border-2 border-bolt-elements-item-contentAccent"
/>
<div className="flex-1">
<h4 className="text-sm font-medium text-bolt-elements-textPrimary">
{connection.user.name || connection.user.login}
</h4>
<p className="text-sm text-bolt-elements-textSecondary">@{connection.user.login}</p>
{connection.user.bio && (
<p className="text-xs text-bolt-elements-textTertiary mt-1 line-clamp-2">{connection.user.bio}</p>
)}
</div>
<div className="text-right">
<div className="text-sm font-medium text-bolt-elements-textPrimary">
{connection.user.public_repos?.toLocaleString() || 0}
</div>
<div className="text-xs text-bolt-elements-textSecondary">repositories</div>
</div>
</div>
</motion.div>
)}
{/* Stats Section */}
{isConnected && connection.stats && (
<Collapsible open={isStatsExpanded} onOpenChange={setIsStatsExpanded}>
<CollapsibleTrigger asChild>
<div className="flex items-center justify-between p-4 rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive transition-all duration-200 cursor-pointer">
<div className="flex items-center gap-2">
<div className="i-ph:chart-bar w-4 h-4 text-bolt-elements-item-contentAccent" />
<span className="text-sm font-medium text-bolt-elements-textPrimary">GitHub Stats</span>
</div>
<div
className={classNames(
'i-ph:caret-down w-4 h-4 transform transition-transform duration-200 text-bolt-elements-textSecondary',
isStatsExpanded ? 'rotate-180' : '',
)}
/>
</div>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden">
<div className="mt-4 p-4 rounded-lg bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor">
<StatsDisplay stats={connection.stats} onRefresh={handleRefreshStats} isRefreshing={isLoadingStats} />
</div>
</CollapsibleContent>
</Collapsible>
)}
{/* Repositories Section */}
{isConnected && connection.stats?.repos && connection.stats.repos.length > 0 && (
<Collapsible open={isReposExpanded} onOpenChange={setIsReposExpanded}>
<CollapsibleTrigger asChild>
<div className="flex items-center justify-between p-4 rounded-lg bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive transition-all duration-200 cursor-pointer">
<div className="flex items-center gap-2">
<div className="i-ph:git-repository w-4 h-4 text-bolt-elements-item-contentAccent" />
<span className="text-sm font-medium text-bolt-elements-textPrimary">
Repositories ({connection.stats.repos.length})
</span>
</div>
<div
className={classNames(
'i-ph:caret-down w-4 h-4 transform transition-transform duration-200 text-bolt-elements-textSecondary',
isReposExpanded ? 'rotate-180' : '',
)}
/>
</div>
</CollapsibleTrigger>
<CollapsibleContent className="overflow-hidden">
<div className="mt-4 p-4 rounded-lg bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor">
<RepositoryList
repositories={connection.stats.repos}
onClone={handleCloneRepository}
onRefresh={handleRefreshStats}
isRefreshing={isLoadingStats}
/>
</div>
</CollapsibleContent>
</Collapsible>
)}
{/* Auth Dialog */}
<AuthDialog isOpen={isAuthDialogOpen} onClose={() => setIsAuthDialogOpen(false)} />
</div>
);
}