* 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>
196 lines
5.7 KiB
TypeScript
196 lines
5.7 KiB
TypeScript
import { atom } from 'nanostores';
|
|
import type { VercelConnection } from '~/types/vercel';
|
|
import { logStore } from './logs';
|
|
import { toast } from 'react-toastify';
|
|
|
|
// Auto-connect using environment variable
|
|
const envToken = import.meta.env?.VITE_VERCEL_ACCESS_TOKEN;
|
|
|
|
// Initialize with stored connection or defaults
|
|
const storedConnection = typeof window !== 'undefined' ? localStorage.getItem('vercel_connection') : null;
|
|
let initialConnection: VercelConnection;
|
|
|
|
if (storedConnection) {
|
|
try {
|
|
const parsed = JSON.parse(storedConnection);
|
|
|
|
// If we have a stored connection but no user and no token, clear it and use env token
|
|
if (!parsed.user && !parsed.token && envToken) {
|
|
console.log('Vercel store: Clearing incomplete saved connection, using env token');
|
|
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.removeItem('vercel_connection');
|
|
}
|
|
|
|
initialConnection = {
|
|
user: null,
|
|
token: envToken,
|
|
stats: undefined,
|
|
};
|
|
} else {
|
|
initialConnection = parsed;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing saved Vercel connection:', error);
|
|
initialConnection = {
|
|
user: null,
|
|
token: envToken || '',
|
|
stats: undefined,
|
|
};
|
|
}
|
|
} else {
|
|
initialConnection = {
|
|
user: null,
|
|
token: envToken || '',
|
|
stats: undefined,
|
|
};
|
|
}
|
|
|
|
export const vercelConnection = atom<VercelConnection>(initialConnection);
|
|
export const isConnecting = atom<boolean>(false);
|
|
export const isFetchingStats = atom<boolean>(false);
|
|
|
|
export const updateVercelConnection = (updates: Partial<VercelConnection>) => {
|
|
const currentState = vercelConnection.get();
|
|
const newState = { ...currentState, ...updates };
|
|
vercelConnection.set(newState);
|
|
|
|
// Persist to localStorage
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem('vercel_connection', JSON.stringify(newState));
|
|
}
|
|
};
|
|
|
|
// Auto-connect using environment token
|
|
export async function autoConnectVercel() {
|
|
console.log('autoConnectVercel called, envToken exists:', !!envToken);
|
|
|
|
if (!envToken) {
|
|
console.error('No Vercel token found in environment');
|
|
return { success: false, error: 'No Vercel token found in environment' };
|
|
}
|
|
|
|
try {
|
|
console.log('Setting isConnecting to true');
|
|
isConnecting.set(true);
|
|
|
|
// Test the connection
|
|
console.log('Making API call to Vercel');
|
|
|
|
const response = await fetch('https://api.vercel.com/v2/user', {
|
|
headers: {
|
|
Authorization: `Bearer ${envToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
console.log('Vercel API response status:', response.status);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Vercel API error: ${response.status}`);
|
|
}
|
|
|
|
const userData = (await response.json()) as any;
|
|
console.log('Vercel API response userData:', userData);
|
|
|
|
// Update connection
|
|
console.log('Updating Vercel connection');
|
|
updateVercelConnection({
|
|
user: userData.user || userData,
|
|
token: envToken,
|
|
});
|
|
|
|
logStore.logInfo('Auto-connected to Vercel', {
|
|
type: 'system',
|
|
message: `Auto-connected to Vercel as ${userData.user?.username || userData.username}`,
|
|
});
|
|
|
|
// Fetch stats
|
|
console.log('Fetching Vercel stats');
|
|
await fetchVercelStats(envToken);
|
|
|
|
console.log('Vercel auto-connection successful');
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Failed to auto-connect to Vercel:', error);
|
|
logStore.logError(`Vercel auto-connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`, {
|
|
type: 'system',
|
|
message: 'Vercel auto-connection failed',
|
|
});
|
|
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
};
|
|
} finally {
|
|
console.log('Setting isConnecting to false');
|
|
isConnecting.set(false);
|
|
}
|
|
}
|
|
|
|
export async function fetchVercelStats(token: string) {
|
|
try {
|
|
isFetchingStats.set(true);
|
|
|
|
const projectsResponse = await fetch('https://api.vercel.com/v9/projects', {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
if (!projectsResponse.ok) {
|
|
throw new Error(`Failed to fetch projects: ${projectsResponse.status}`);
|
|
}
|
|
|
|
const projectsData = (await projectsResponse.json()) as any;
|
|
const projects = projectsData.projects || [];
|
|
|
|
// Fetch latest deployment for each project
|
|
const projectsWithDeployments = await Promise.all(
|
|
projects.map(async (project: any) => {
|
|
try {
|
|
const deploymentsResponse = await fetch(
|
|
`https://api.vercel.com/v6/deployments?projectId=${project.id}&limit=1`,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
},
|
|
);
|
|
|
|
if (deploymentsResponse.ok) {
|
|
const deploymentsData = (await deploymentsResponse.json()) as any;
|
|
return {
|
|
...project,
|
|
latestDeployments: deploymentsData.deployments || [],
|
|
};
|
|
}
|
|
|
|
return project;
|
|
} catch (error) {
|
|
console.error(`Error fetching deployments for project ${project.id}:`, error);
|
|
return project;
|
|
}
|
|
}),
|
|
);
|
|
|
|
const currentState = vercelConnection.get();
|
|
updateVercelConnection({
|
|
...currentState,
|
|
stats: {
|
|
projects: projectsWithDeployments,
|
|
totalProjects: projectsWithDeployments.length,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('Vercel API Error:', error);
|
|
logStore.logError('Failed to fetch Vercel stats', { error });
|
|
toast.error('Failed to fetch Vercel statistics');
|
|
} finally {
|
|
isFetchingStats.set(false);
|
|
}
|
|
}
|