connection github enhancements

This commit is contained in:
Stijnus
2025-01-23 16:02:50 +01:00
parent 5a6c0e5f90
commit fd98059cfe
6 changed files with 422 additions and 26 deletions

View File

@@ -8,11 +8,34 @@ interface GitHubUserResponse {
login: string;
avatar_url: string;
html_url: string;
name: string;
bio: string;
public_repos: number;
followers: number;
following: number;
}
interface GitHubRepoInfo {
name: string;
full_name: string;
html_url: string;
description: string;
stargazers_count: number;
forks_count: number;
default_branch: string;
updated_at: string;
}
interface GitHubStats {
repos: GitHubRepoInfo[];
totalStars: number;
totalForks: number;
}
interface GitHubConnection {
user: GitHubUserResponse | null;
token: string;
stats?: GitHubStats;
}
export default function ConnectionsTab() {
@@ -22,18 +45,59 @@ export default function ConnectionsTab() {
});
const [isLoading, setIsLoading] = useState(true);
const [isConnecting, setIsConnecting] = useState(false);
const [isFetchingStats, setIsFetchingStats] = useState(false);
// Load saved connection on mount
useEffect(() => {
const savedConnection = localStorage.getItem('github_connection');
if (savedConnection) {
setConnection(JSON.parse(savedConnection));
const parsed = JSON.parse(savedConnection);
setConnection(parsed);
if (parsed.user && parsed.token) {
fetchGitHubStats(parsed.token);
}
}
setIsLoading(false);
}, []);
const fetchGitHubStats = async (token: string) => {
try {
setIsFetchingStats(true);
// Fetch repositories
const reposResponse = await fetch('https://api.github.com/user/repos?sort=updated&per_page=10', {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!reposResponse.ok) throw new Error('Failed to fetch repositories');
const repos = await reposResponse.json() as GitHubRepoInfo[];
// Calculate total stats
const totalStars = repos.reduce((acc, repo) => acc + repo.stargazers_count, 0);
const totalForks = repos.reduce((acc, repo) => acc + repo.forks_count, 0);
setConnection(prev => ({
...prev,
stats: {
repos,
totalStars,
totalForks,
},
}));
} catch (error) {
logStore.logError('Failed to fetch GitHub stats', { error });
toast.error('Failed to fetch GitHub statistics');
} finally {
setIsFetchingStats(false);
}
};
const fetchGithubUser = async (token: string) => {
try {
setIsConnecting(true);
@@ -44,16 +108,18 @@ export default function ConnectionsTab() {
},
});
if (!response.ok) {
throw new Error('Invalid token or unauthorized');
}
if (!response.ok) throw new Error('Invalid token or unauthorized');
const data = (await response.json()) as GitHubUserResponse;
const data = await response.json() as GitHubUserResponse;
const newConnection = { user: data, token };
// Save connection
localStorage.setItem('github_connection', JSON.stringify(newConnection));
setConnection(newConnection);
// Fetch additional stats
await fetchGitHubStats(token);
toast.success('Successfully connected to GitHub');
} catch (error) {
logStore.logError('Failed to authenticate with GitHub', { error });
@@ -75,16 +141,7 @@ export default function ConnectionsTab() {
toast.success('Disconnected from GitHub');
};
if (isLoading) {
return (
<div className="flex items-center justify-center p-4">
<div className="flex items-center gap-2">
<div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
<span className="text-bolt-elements-textSecondary">Loading...</span>
</div>
</div>
);
}
if (isLoading) return <LoadingSpinner />;
return (
<div className="space-y-4">
@@ -200,9 +257,92 @@ export default function ConnectionsTab() {
</span>
)}
</div>
{connection.user && connection.stats && (
<div className="mt-6 border-t border-[#E5E5E5] dark:border-[#1A1A1A] pt-6">
<div className="flex items-center gap-4 mb-6">
<img
src={connection.user.avatar_url}
alt={connection.user.login}
className="w-16 h-16 rounded-full"
/>
<div>
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">
{connection.user.name || connection.user.login}
</h3>
{connection.user.bio && (
<p className="text-sm text-bolt-elements-textSecondary">{connection.user.bio}</p>
)}
<div className="flex gap-4 mt-2 text-sm text-bolt-elements-textSecondary">
<span className="flex items-center gap-1">
<div className="i-ph:users w-4 h-4" />
{connection.user.followers} followers
</span>
<span className="flex items-center gap-1">
<div className="i-ph:star w-4 h-4" />
{connection.stats.totalStars} stars
</span>
<span className="flex items-center gap-1">
<div className="i-ph:git-fork w-4 h-4" />
{connection.stats.totalForks} forks
</span>
</div>
</div>
</div>
<h4 className="text-sm font-medium text-bolt-elements-textPrimary mb-3">
Recent Repositories
</h4>
<div className="space-y-3">
{connection.stats.repos.map((repo) => (
<a
key={repo.full_name}
href={repo.html_url}
target="_blank"
rel="noopener noreferrer"
className="block p-3 rounded-lg bg-[#F8F8F8] dark:bg-[#1A1A1A] hover:bg-[#F0F0F0] dark:hover:bg-[#252525] transition-colors"
>
<div className="flex items-center justify-between">
<div>
<h5 className="text-sm font-medium text-bolt-elements-textPrimary">
{repo.name}
</h5>
{repo.description && (
<p className="text-xs text-bolt-elements-textSecondary mt-1">
{repo.description}
</p>
)}
</div>
<div className="flex items-center gap-3 text-xs text-bolt-elements-textSecondary">
<span className="flex items-center gap-1">
<div className="i-ph:star w-3 h-3" />
{repo.stargazers_count}
</span>
<span className="flex items-center gap-1">
<div className="i-ph:git-fork w-3 h-3" />
{repo.forks_count}
</span>
</div>
</div>
</a>
))}
</div>
</div>
)}
</div>
</motion.div>
</div>
</div>
);
}
function LoadingSpinner() {
return (
<div className="flex items-center justify-center p-4">
<div className="flex items-center gap-2">
<div className="i-ph:spinner-gap-bold animate-spin w-4 h-4" />
<span className="text-bolt-elements-textSecondary">Loading...</span>
</div>
</div>
);
}

View File

@@ -84,6 +84,7 @@ interface SystemInfo {
}
interface WebAppInfo {
// Local WebApp Info
name: string;
version: string;
description: string;
@@ -91,6 +92,33 @@ interface WebAppInfo {
nodeVersion: string;
dependencies: { [key: string]: string };
devDependencies: { [key: string]: string };
// Build Info
buildTime?: string;
buildNumber?: string;
environment?: string;
// Git Info
gitInfo?: {
branch: string;
commit: string;
commitTime: string;
author: string;
remoteUrl: string;
};
// GitHub Repository Info
repoInfo?: {
name: string;
fullName: string;
description: string;
stars: number;
forks: number;
openIssues: number;
defaultBranch: string;
lastUpdate: string;
owner: {
login: string;
avatarUrl: string;
};
};
}
export default function DebugTab() {
@@ -298,14 +326,54 @@ export default function DebugTab() {
try {
setLoading((prev) => ({ ...prev, webAppInfo: true }));
const response = await fetch('/api/system/app-info');
if (!response.ok) {
// Fetch local app info
const appInfoResponse = await fetch('/api/system/app-info');
if (!appInfoResponse.ok) {
throw new Error('Failed to fetch webapp info');
}
const appData = await appInfoResponse.json();
const data = await response.json();
setWebAppInfo(data as WebAppInfo);
// Fetch git info
const gitInfoResponse = await fetch('/api/system/git-info');
let gitInfo = null;
if (gitInfoResponse.ok) {
gitInfo = await gitInfoResponse.json();
}
// Fetch GitHub repository info
const repoInfoResponse = await fetch('https://api.github.com/repos/stackblitz-labs/bolt.diy');
let repoInfo = null;
if (repoInfoResponse.ok) {
const repoData = await repoInfoResponse.json();
repoInfo = {
name: repoData.name,
fullName: repoData.full_name,
description: repoData.description,
stars: repoData.stargazers_count,
forks: repoData.forks_count,
openIssues: repoData.open_issues_count,
defaultBranch: repoData.default_branch,
lastUpdate: repoData.updated_at,
owner: {
login: repoData.owner.login,
avatarUrl: repoData.owner.avatar_url,
},
};
}
// Get build info from environment variables or config
const buildInfo = {
buildTime: process.env.NEXT_PUBLIC_BUILD_TIME || new Date().toISOString(),
buildNumber: process.env.NEXT_PUBLIC_BUILD_NUMBER || 'development',
environment: process.env.NEXT_PUBLIC_ENV || 'development',
};
setWebAppInfo({
...appData,
...buildInfo,
gitInfo,
repoInfo,
});
} catch (error) {
console.error('Failed to fetch webapp info:', error);
toast.error('Failed to fetch webapp information');
@@ -895,6 +963,27 @@ export default function DebugTab() {
<span className="text-bolt-elements-textSecondary">Node Version: </span>
<span className="text-bolt-elements-textPrimary">{webAppInfo.nodeVersion}</span>
</div>
{webAppInfo.buildTime && (
<div className="text-sm flex items-center gap-2">
<div className="i-ph:calendar text-bolt-elements-textSecondary w-4 h-4" />
<span className="text-bolt-elements-textSecondary">Build Time: </span>
<span className="text-bolt-elements-textPrimary">{webAppInfo.buildTime}</span>
</div>
)}
{webAppInfo.buildNumber && (
<div className="text-sm flex items-center gap-2">
<div className="i-ph:hash text-bolt-elements-textSecondary w-4 h-4" />
<span className="text-bolt-elements-textSecondary">Build Number: </span>
<span className="text-bolt-elements-textPrimary">{webAppInfo.buildNumber}</span>
</div>
)}
{webAppInfo.environment && (
<div className="text-sm flex items-center gap-2">
<div className="i-ph:cloud text-bolt-elements-textSecondary w-4 h-4" />
<span className="text-bolt-elements-textSecondary">Environment: </span>
<span className="text-bolt-elements-textPrimary">{webAppInfo.environment}</span>
</div>
)}
</div>
<div className="space-y-2">
<div className="text-sm">
@@ -912,6 +1001,71 @@ export default function DebugTab() {
))}
</div>
</div>
{webAppInfo.gitInfo && (
<div className="text-sm">
<div className="flex items-center gap-2 mb-2">
<div className="i-ph:git-branch text-bolt-elements-textSecondary w-4 h-4" />
<span className="text-bolt-elements-textSecondary">Git Info:</span>
</div>
<div className="pl-6 space-y-1">
<div className="text-xs text-bolt-elements-textPrimary">
Branch: {webAppInfo.gitInfo.branch}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Commit: {webAppInfo.gitInfo.commit}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Commit Time: {webAppInfo.gitInfo.commitTime}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Author: {webAppInfo.gitInfo.author}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Remote URL: {webAppInfo.gitInfo.remoteUrl}
</div>
</div>
</div>
)}
{webAppInfo.repoInfo && (
<div className="text-sm">
<div className="flex items-center gap-2 mb-2">
<div className="i-ph:github text-bolt-elements-textSecondary w-4 h-4" />
<span className="text-bolt-elements-textSecondary">GitHub Repository:</span>
</div>
<div className="pl-6 space-y-1">
<div className="text-xs text-bolt-elements-textPrimary">
Name: {webAppInfo.repoInfo.name}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Full Name: {webAppInfo.repoInfo.fullName}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Description: {webAppInfo.repoInfo.description}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Stars: {webAppInfo.repoInfo.stars}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Forks: {webAppInfo.repoInfo.forks}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Open Issues: {webAppInfo.repoInfo.openIssues}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Default Branch: {webAppInfo.repoInfo.defaultBranch}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Last Update: {webAppInfo.repoInfo.lastUpdate}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Owner: {webAppInfo.repoInfo.owner.login}
</div>
<div className="text-xs text-bolt-elements-textPrimary">
Avatar URL: {webAppInfo.repoInfo.owner.avatarUrl}
</div>
</div>
</div>
)}
</div>
</div>
) : (