Files
bolt-diy/app/lib/stores/githubConnection.ts
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

227 lines
6.3 KiB
TypeScript

import { atom, computed } from 'nanostores';
import Cookies from 'js-cookie';
import { logStore } from '~/lib/stores/logs';
import { gitHubApiService } from '~/lib/services/githubApiService';
import { calculateStatsSummary } from '~/utils/githubStats';
import type { GitHubConnection } from '~/types/GitHub';
// Auto-connect using environment variable
const envToken = import.meta.env?.VITE_GITHUB_ACCESS_TOKEN;
const envTokenType = import.meta.env?.VITE_GITHUB_TOKEN_TYPE;
const githubConnectionAtom = atom<GitHubConnection>({
user: null,
token: envToken || '',
tokenType:
envTokenType === 'classic' || envTokenType === 'fine-grained'
? (envTokenType as 'classic' | 'fine-grained')
: 'classic',
});
// Initialize connection from localStorage on startup
function initializeConnection() {
try {
const savedConnection = localStorage.getItem('github_connection');
if (savedConnection) {
const parsed = JSON.parse(savedConnection);
// Ensure tokenType is set
if (!parsed.tokenType) {
parsed.tokenType = 'classic';
}
// Only set if we have a valid user
if (parsed.user) {
githubConnectionAtom.set(parsed);
}
}
} catch (error) {
console.error('Error initializing GitHub connection:', error);
localStorage.removeItem('github_connection');
}
}
// Initialize on module load (client-side only)
if (typeof window !== 'undefined') {
initializeConnection();
}
// Computed store for checking if connected
export const isGitHubConnected = computed(githubConnectionAtom, (connection) => !!connection.user);
// Computed store for GitHub stats summary
export const githubStatsSummary = computed(githubConnectionAtom, (connection) => {
if (!connection.stats) {
return null;
}
return calculateStatsSummary(connection.stats);
});
// Connection status atoms
export const isGitHubConnecting = atom(false);
export const isGitHubLoadingStats = atom(false);
// GitHub connection store methods
export const githubConnectionStore = {
// Get current connection
get: () => githubConnectionAtom.get(),
// Connect to GitHub
async connect(token: string, tokenType: 'classic' | 'fine-grained' = 'classic'): Promise<void> {
if (isGitHubConnecting.get()) {
throw new Error('Connection already in progress');
}
isGitHubConnecting.set(true);
try {
// Fetch user data
const { user, rateLimit } = await gitHubApiService.fetchUser(token, tokenType);
// Create connection object
const connection: GitHubConnection = {
user,
token,
tokenType,
rateLimit,
};
// Set cookies for client-side access
Cookies.set('githubUsername', user.login);
Cookies.set('githubToken', token);
Cookies.set('git:github.com', JSON.stringify({ username: token, password: 'x-oauth-basic' }));
// Store connection details in localStorage
localStorage.setItem('github_connection', JSON.stringify(connection));
// Update atom
githubConnectionAtom.set(connection);
logStore.logInfo('Connected to GitHub', {
type: 'system',
message: `Connected to GitHub as ${user.login}`,
});
// Fetch stats in background
this.fetchStats().catch((error) => {
console.error('Failed to fetch initial GitHub stats:', error);
});
} catch (error) {
console.error('Failed to connect to GitHub:', error);
logStore.logError(`GitHub authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
type: 'system',
message: 'GitHub authentication failed',
});
throw error;
} finally {
isGitHubConnecting.set(false);
}
},
// Disconnect from GitHub
disconnect(): void {
// Clear atoms
githubConnectionAtom.set({
user: null,
token: '',
tokenType: 'classic',
});
// Clear localStorage
localStorage.removeItem('github_connection');
// Clear cookies
Cookies.remove('githubUsername');
Cookies.remove('githubToken');
Cookies.remove('git:github.com');
// Clear API service cache
gitHubApiService.clearCache();
logStore.logInfo('Disconnected from GitHub', {
type: 'system',
message: 'Disconnected from GitHub',
});
},
// Fetch GitHub stats
async fetchStats(): Promise<void> {
const connection = githubConnectionAtom.get();
if (!connection.user || !connection.token) {
throw new Error('Not connected to GitHub');
}
if (isGitHubLoadingStats.get()) {
return; // Already loading
}
isGitHubLoadingStats.set(true);
try {
const stats = await gitHubApiService.fetchStats(connection.token, connection.tokenType);
// Update connection with stats
const updatedConnection: GitHubConnection = {
...connection,
stats,
};
// Update localStorage
localStorage.setItem('github_connection', JSON.stringify(updatedConnection));
// Update atom
githubConnectionAtom.set(updatedConnection);
logStore.logInfo('GitHub stats refreshed', {
type: 'system',
message: 'Successfully refreshed GitHub statistics',
});
} catch (error) {
console.error('Failed to fetch GitHub stats:', error);
// If the error is due to expired token, disconnect
if (error instanceof Error && error.message.includes('401')) {
logStore.logError('GitHub token has expired', {
type: 'system',
message: 'GitHub token has expired. Please reconnect your account.',
});
this.disconnect();
}
throw error;
} finally {
isGitHubLoadingStats.set(false);
}
},
// Update token type
updateTokenType(tokenType: 'classic' | 'fine-grained'): void {
const connection = githubConnectionAtom.get();
const updatedConnection = {
...connection,
tokenType,
};
githubConnectionAtom.set(updatedConnection);
localStorage.setItem('github_connection', JSON.stringify(updatedConnection));
},
// Clear stats cache
clearCache(): void {
const connection = githubConnectionAtom.get();
if (connection.token) {
gitHubApiService.clearUserCache(connection.token);
}
},
// Subscribe to connection changes
subscribe: githubConnectionAtom.subscribe.bind(githubConnectionAtom),
};
// Export the atom for direct access
export { githubConnectionAtom };