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>
This commit is contained in:
Stijnus
2025-09-05 14:01:33 +02:00
committed by GitHub
parent 8a685603be
commit 3ea96506ea
46 changed files with 4401 additions and 4025 deletions

View File

@@ -0,0 +1,300 @@
import { atom, computed } from 'nanostores';
import Cookies from 'js-cookie';
import { logStore } from '~/lib/stores/logs';
import { GitLabApiService } from '~/lib/services/gitlabApiService';
import { calculateStatsSummary } from '~/utils/gitlabStats';
import type { GitLabConnection, GitLabStats } from '~/types/GitLab';
// Auto-connect using environment variable
const envToken = import.meta.env?.VITE_GITLAB_ACCESS_TOKEN;
const gitlabConnectionAtom = atom<GitLabConnection>({
user: null,
token: envToken || '',
tokenType: 'personal-access-token',
});
const gitlabUrlAtom = atom('https://gitlab.com');
// Initialize connection from localStorage on startup
function initializeConnection() {
try {
const savedConnection = localStorage.getItem('gitlab_connection');
if (savedConnection) {
const parsed = JSON.parse(savedConnection);
parsed.tokenType = 'personal-access-token';
if (parsed.gitlabUrl) {
gitlabUrlAtom.set(parsed.gitlabUrl);
}
// Only set if we have a valid user
if (parsed.user) {
gitlabConnectionAtom.set(parsed);
}
}
} catch (error) {
console.error('Error initializing GitLab connection:', error);
localStorage.removeItem('gitlab_connection');
}
}
// Initialize on module load (client-side only)
if (typeof window !== 'undefined') {
initializeConnection();
}
// Computed store for checking if connected
export const isGitLabConnected = computed(gitlabConnectionAtom, (connection) => !!connection.user);
// Computed store for current connection
export const gitlabConnection = computed(gitlabConnectionAtom, (connection) => connection);
// Computed store for current user
export const gitlabUser = computed(gitlabConnectionAtom, (connection) => connection.user);
// Computed store for current stats
export const gitlabStats = computed(gitlabConnectionAtom, (connection) => connection.stats);
// Computed store for current URL
export const gitlabUrl = computed(gitlabUrlAtom, (url) => url);
class GitLabConnectionStore {
async connect(token: string, gitlabUrl = 'https://gitlab.com') {
try {
const apiService = new GitLabApiService(token, gitlabUrl);
// Test connection by fetching user
const user = await apiService.getUser();
// Update state
gitlabConnectionAtom.set({
user,
token,
tokenType: 'personal-access-token',
gitlabUrl,
});
// Set cookies for client-side access
Cookies.set('gitlabUsername', user.username);
Cookies.set('gitlabToken', token);
Cookies.set('git:gitlab.com', JSON.stringify({ username: user.username, password: token }));
Cookies.set('gitlabUrl', gitlabUrl);
// Store connection details in localStorage
localStorage.setItem(
'gitlab_connection',
JSON.stringify({
user,
token,
tokenType: 'personal-access-token',
gitlabUrl,
}),
);
logStore.logInfo('Connected to GitLab', {
type: 'system',
message: `Connected to GitLab as ${user.username}`,
});
return { success: true };
} catch (error) {
console.error('Failed to connect to GitLab:', error);
logStore.logError(`GitLab authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
type: 'system',
message: 'GitLab authentication failed',
});
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
async fetchStats(_forceRefresh = false) {
const connection = gitlabConnectionAtom.get();
if (!connection.user || !connection.token) {
throw new Error('Not connected to GitLab');
}
try {
const apiService = new GitLabApiService(connection.token, connection.gitlabUrl || 'https://gitlab.com');
// Fetch user data
const userData = await apiService.getUser();
// Fetch projects
const projects = await apiService.getProjects();
// Fetch events
const events = await apiService.getEvents();
// Fetch groups
const groups = await apiService.getGroups();
// Fetch snippets
const snippets = await apiService.getSnippets();
// Calculate stats
const stats: GitLabStats = calculateStatsSummary(projects, events, groups, snippets, userData);
// Update connection with stats
gitlabConnectionAtom.set({
...connection,
stats,
});
// Update localStorage
const updatedConnection = { ...connection, stats };
localStorage.setItem('gitlab_connection', JSON.stringify(updatedConnection));
return { success: true, stats };
} catch (error) {
console.error('Error fetching GitLab stats:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
disconnect() {
// Remove cookies
Cookies.remove('gitlabToken');
Cookies.remove('gitlabUsername');
Cookies.remove('git:gitlab.com');
Cookies.remove('gitlabUrl');
// Clear localStorage
localStorage.removeItem('gitlab_connection');
// Reset state
gitlabConnectionAtom.set({
user: null,
token: '',
tokenType: 'personal-access-token',
});
logStore.logInfo('Disconnected from GitLab', {
type: 'system',
message: 'Disconnected from GitLab',
});
}
loadSavedConnection() {
try {
const savedConnection = localStorage.getItem('gitlab_connection');
if (savedConnection) {
const parsed = JSON.parse(savedConnection);
parsed.tokenType = 'personal-access-token';
// Set GitLab URL if saved
if (parsed.gitlabUrl) {
gitlabUrlAtom.set(parsed.gitlabUrl);
}
// Set connection
gitlabConnectionAtom.set(parsed);
return parsed;
}
} catch (error) {
console.error('Error parsing saved GitLab connection:', error);
localStorage.removeItem('gitlab_connection');
}
return null;
}
setGitLabUrl(url: string) {
gitlabUrlAtom.set(url);
}
setToken(token: string) {
gitlabConnectionAtom.set({
...gitlabConnectionAtom.get(),
token,
});
}
// Auto-connect using environment token
async autoConnect() {
if (!envToken) {
return { success: false, error: 'No GitLab token found in environment' };
}
try {
const apiService = new GitLabApiService(envToken);
const user = await apiService.getUser();
// Update state
gitlabConnectionAtom.set({
user,
token: envToken,
tokenType: 'personal-access-token',
gitlabUrl: 'https://gitlab.com',
});
// Set cookies for client-side access
Cookies.set('gitlabUsername', user.username);
Cookies.set('gitlabToken', envToken);
Cookies.set('git:gitlab.com', JSON.stringify({ username: user.username, password: envToken }));
Cookies.set('gitlabUrl', 'https://gitlab.com');
// Store connection details in localStorage
localStorage.setItem(
'gitlab_connection',
JSON.stringify({
user,
token: envToken,
tokenType: 'personal-access-token',
gitlabUrl: 'https://gitlab.com',
}),
);
logStore.logInfo('Auto-connected to GitLab', {
type: 'system',
message: `Auto-connected to GitLab as ${user.username}`,
});
return { success: true };
} catch (error) {
console.error('Failed to auto-connect to GitLab:', error);
logStore.logError(`GitLab auto-connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
type: 'system',
message: 'GitLab auto-connection failed',
});
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
}
export const gitlabConnectionStore = new GitLabConnectionStore();
// Export hooks for React components
export function useGitLabConnection() {
return {
connection: gitlabConnection,
isConnected: isGitLabConnected,
user: gitlabUser,
stats: gitlabStats,
gitlabUrl,
connect: gitlabConnectionStore.connect.bind(gitlabConnectionStore),
disconnect: gitlabConnectionStore.disconnect.bind(gitlabConnectionStore),
fetchStats: gitlabConnectionStore.fetchStats.bind(gitlabConnectionStore),
loadSavedConnection: gitlabConnectionStore.loadSavedConnection.bind(gitlabConnectionStore),
setGitLabUrl: gitlabConnectionStore.setGitLabUrl.bind(gitlabConnectionStore),
setToken: gitlabConnectionStore.setToken.bind(gitlabConnectionStore),
autoConnect: gitlabConnectionStore.autoConnect.bind(gitlabConnectionStore),
};
}