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:
300
app/lib/stores/gitlabConnection.ts
Normal file
300
app/lib/stores/gitlabConnection.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user