feat: add Vercel integration for project deployment

This commit introduces Vercel integration, enabling users to deploy projects directly to Vercel. It includes:
- New Vercel types and store for managing connections and stats.
- A VercelConnection component for managing Vercel account connections.
- A VercelDeploymentLink component for displaying deployment links.
- API routes for handling Vercel deployments.
- Updates to the HeaderActionButtons component to support Vercel deployment.

The integration allows users to connect their Vercel accounts, view project stats, and deploy projects with ease.
This commit is contained in:
KevIsDev
2025-03-27 00:06:10 +00:00
parent 1364d4a503
commit 687b03ba74
9 changed files with 982 additions and 20 deletions

94
app/lib/stores/vercel.ts Normal file
View File

@@ -0,0 +1,94 @@
import { atom } from 'nanostores';
import type { VercelConnection } from '~/types/vercel';
import { logStore } from './logs';
import { toast } from 'react-toastify';
// Initialize with stored connection or defaults
const storedConnection = typeof window !== 'undefined' ? localStorage.getItem('vercel_connection') : null;
const initialConnection: VercelConnection = storedConnection
? JSON.parse(storedConnection)
: {
user: null,
token: '',
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));
}
};
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);
}
}