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

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);
}
}