UI fixes
This commit is contained in:
@@ -35,6 +35,7 @@ interface GitHubStats {
|
||||
interface GitHubConnection {
|
||||
user: GitHubUserResponse | null;
|
||||
token: string;
|
||||
tokenType: 'classic' | 'fine-grained';
|
||||
stats?: GitHubStats;
|
||||
}
|
||||
|
||||
@@ -42,6 +43,7 @@ export default function ConnectionsTab() {
|
||||
const [connection, setConnection] = useState<GitHubConnection>({
|
||||
user: null,
|
||||
token: '',
|
||||
tokenType: 'classic',
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isConnecting, setIsConnecting] = useState(false);
|
||||
@@ -53,7 +55,14 @@ export default function ConnectionsTab() {
|
||||
|
||||
if (savedConnection) {
|
||||
const parsed = JSON.parse(savedConnection);
|
||||
|
||||
// Ensure backward compatibility with existing connections
|
||||
if (!parsed.tokenType) {
|
||||
parsed.tokenType = 'classic';
|
||||
}
|
||||
|
||||
setConnection(parsed);
|
||||
|
||||
if (parsed.user && parsed.token) {
|
||||
fetchGitHubStats(parsed.token);
|
||||
}
|
||||
@@ -73,7 +82,9 @@ export default function ConnectionsTab() {
|
||||
},
|
||||
});
|
||||
|
||||
if (!reposResponse.ok) throw new Error('Failed to fetch repositories');
|
||||
if (!reposResponse.ok) {
|
||||
throw new Error('Failed to fetch repositories');
|
||||
}
|
||||
|
||||
const repos = (await reposResponse.json()) as GitHubRepoInfo[];
|
||||
|
||||
@@ -107,10 +118,16 @@ 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 newConnection = { user: data, token };
|
||||
const newConnection: GitHubConnection = {
|
||||
user: data,
|
||||
token,
|
||||
tokenType: connection.tokenType,
|
||||
};
|
||||
|
||||
// Save connection
|
||||
localStorage.setItem('github_connection', JSON.stringify(newConnection));
|
||||
@@ -123,7 +140,7 @@ export default function ConnectionsTab() {
|
||||
} catch (error) {
|
||||
logStore.logError('Failed to authenticate with GitHub', { error });
|
||||
toast.error('Failed to connect to GitHub');
|
||||
setConnection({ user: null, token: '' });
|
||||
setConnection({ user: null, token: '', tokenType: 'classic' });
|
||||
} finally {
|
||||
setIsConnecting(false);
|
||||
}
|
||||
@@ -136,11 +153,13 @@ export default function ConnectionsTab() {
|
||||
|
||||
const handleDisconnect = () => {
|
||||
localStorage.removeItem('github_connection');
|
||||
setConnection({ user: null, token: '' });
|
||||
setConnection({ user: null, token: '', tokenType: 'classic' });
|
||||
toast.success('Disconnected from GitHub');
|
||||
};
|
||||
|
||||
if (isLoading) return <LoadingSpinner />;
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -174,31 +193,37 @@ export default function ConnectionsTab() {
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm text-bolt-elements-textSecondary mb-2">GitHub Username</label>
|
||||
<input
|
||||
type="text"
|
||||
value={connection.user?.login || ''}
|
||||
disabled={true}
|
||||
placeholder="Not connected"
|
||||
<label className="block text-sm text-bolt-elements-textSecondary mb-2">Token Type</label>
|
||||
<select
|
||||
value={connection.tokenType}
|
||||
onChange={(e) =>
|
||||
setConnection((prev) => ({ ...prev, tokenType: e.target.value as 'classic' | 'fine-grained' }))
|
||||
}
|
||||
disabled={isConnecting || !!connection.user}
|
||||
className={classNames(
|
||||
'w-full px-3 py-2 rounded-lg text-sm',
|
||||
'bg-[#F8F8F8] dark:bg-[#1A1A1A]',
|
||||
'border border-[#E5E5E5] dark:border-[#333333]',
|
||||
'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary',
|
||||
'text-bolt-elements-textPrimary',
|
||||
'focus:outline-none focus:ring-1 focus:ring-purple-500',
|
||||
'disabled:opacity-50',
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<option value="classic">Personal Access Token (Classic)</option>
|
||||
<option value="fine-grained">Fine-grained Token</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-bolt-elements-textSecondary mb-2">Personal Access Token</label>
|
||||
<label className="block text-sm text-bolt-elements-textSecondary mb-2">
|
||||
{connection.tokenType === 'classic' ? 'Personal Access Token' : 'Fine-grained Token'}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={connection.token}
|
||||
onChange={(e) => setConnection((prev) => ({ ...prev, token: e.target.value }))}
|
||||
disabled={isConnecting || !!connection.user}
|
||||
placeholder="Enter your GitHub token"
|
||||
placeholder={`Enter your GitHub ${connection.tokenType === 'classic' ? 'personal access token' : 'fine-grained token'}`}
|
||||
className={classNames(
|
||||
'w-full px-3 py-2 rounded-lg text-sm',
|
||||
'bg-[#F8F8F8] dark:bg-[#1A1A1A]',
|
||||
@@ -257,69 +282,49 @@ export default function ConnectionsTab() {
|
||||
)}
|
||||
</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">
|
||||
{connection.user && (
|
||||
<div className="p-4 bg-[#F8F8F8] dark:bg-[#1A1A1A] rounded-lg">
|
||||
<div className="flex items-center gap-4">
|
||||
<img
|
||||
src={connection.user.avatar_url}
|
||||
alt={connection.user.login}
|
||||
className="w-16 h-16 rounded-full"
|
||||
className="w-12 h-12 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>
|
||||
<h4 className="text-sm font-medium text-bolt-elements-textPrimary">{connection.user.name}</h4>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">@{connection.user.login}</p>
|
||||
</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>
|
||||
{isFetchingStats ? (
|
||||
<div className="mt-4 flex items-center gap-2 text-sm text-bolt-elements-textSecondary">
|
||||
<div className="i-ph:spinner-gap w-4 h-4 animate-spin" />
|
||||
Fetching GitHub stats...
|
||||
</div>
|
||||
) : (
|
||||
connection.stats && (
|
||||
<div className="mt-4 grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">Public Repos</p>
|
||||
<p className="text-lg font-medium text-bolt-elements-textPrimary">
|
||||
{connection.user.public_repos}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">Total Stars</p>
|
||||
<p className="text-lg font-medium text-bolt-elements-textPrimary">
|
||||
{connection.stats.totalStars}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">Total Forks</p>
|
||||
<p className="text-lg font-medium text-bolt-elements-textPrimary">
|
||||
{connection.stats.totalForks}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import type { GitHubAuthState } from '~/components/settings/connections/types/GitHub';
|
||||
import Cookies from 'js-cookie';
|
||||
import { getLocalStorage } from '~/utils/localStorage';
|
||||
|
||||
const GITHUB_TOKEN_KEY = 'github_token';
|
||||
|
||||
interface ConnectionFormProps {
|
||||
authState: GitHubAuthState;
|
||||
setAuthState: React.Dispatch<React.SetStateAction<GitHubAuthState>>;
|
||||
onSave: (e: React.FormEvent) => void;
|
||||
onDisconnect: () => void;
|
||||
}
|
||||
|
||||
export function ConnectionForm({ authState, setAuthState, onSave, onDisconnect }: ConnectionFormProps) {
|
||||
// Check for saved token on mount
|
||||
useEffect(() => {
|
||||
const savedToken = Cookies.get(GITHUB_TOKEN_KEY) || getLocalStorage(GITHUB_TOKEN_KEY);
|
||||
|
||||
if (savedToken && !authState.tokenInfo?.token) {
|
||||
setAuthState((prev: GitHubAuthState) => ({
|
||||
...prev,
|
||||
tokenInfo: {
|
||||
token: savedToken,
|
||||
scope: [],
|
||||
avatar_url: '',
|
||||
name: null,
|
||||
created_at: new Date().toISOString(),
|
||||
followers: 0,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-white dark:bg-[#0A0A0A] border border-[#E5E5E5] dark:border-[#1A1A1A] overflow-hidden">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] border border-[#E5E5E5] dark:border-[#1A1A1A]">
|
||||
<div className="i-ph:plug-fill text-bolt-elements-textTertiary" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-bolt-elements-textPrimary">Connection Settings</h3>
|
||||
<p className="text-sm text-bolt-elements-textSecondary">Configure your GitHub connection</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSave} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="username" className="block text-sm font-medium text-bolt-elements-textSecondary mb-2">
|
||||
GitHub Username
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={authState.username}
|
||||
onChange={(e) => setAuthState((prev: GitHubAuthState) => ({ ...prev, username: e.target.value }))}
|
||||
className={classNames(
|
||||
'w-full px-4 py-2.5 bg-[#F5F5F5] dark:bg-[#1A1A1A] border rounded-lg',
|
||||
'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary text-base',
|
||||
'border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||
'focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
placeholder="e.g., octocat"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label htmlFor="token" className="block text-sm font-medium text-bolt-elements-textSecondary">
|
||||
Personal Access Token
|
||||
</label>
|
||||
<a
|
||||
href="https://github.com/settings/tokens/new?scopes=repo,user,read:org,workflow,delete_repo,write:packages,read:packages"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={classNames(
|
||||
'inline-flex items-center gap-1.5 text-xs',
|
||||
'text-purple-500 hover:text-purple-600 dark:text-purple-400 dark:hover:text-purple-300',
|
||||
'transition-colors duration-200',
|
||||
)}
|
||||
>
|
||||
<span>Generate new token</span>
|
||||
<div className="i-ph:plus-circle" />
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
id="token"
|
||||
type="password"
|
||||
value={authState.tokenInfo?.token || ''}
|
||||
onChange={(e) =>
|
||||
setAuthState((prev: GitHubAuthState) => ({
|
||||
...prev,
|
||||
tokenInfo: {
|
||||
token: e.target.value,
|
||||
scope: [],
|
||||
avatar_url: '',
|
||||
name: null,
|
||||
created_at: new Date().toISOString(),
|
||||
followers: 0,
|
||||
},
|
||||
username: '',
|
||||
isConnected: false,
|
||||
isVerifying: false,
|
||||
isLoadingRepos: false,
|
||||
}))
|
||||
}
|
||||
className={classNames(
|
||||
'w-full px-4 py-2.5 bg-[#F5F5F5] dark:bg-[#1A1A1A] border rounded-lg',
|
||||
'text-bolt-elements-textPrimary placeholder-bolt-elements-textTertiary text-base',
|
||||
'border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||
'focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500',
|
||||
'transition-all duration-200',
|
||||
)}
|
||||
placeholder="ghp_xxxxxxxxxxxx"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-4 border-t border-[#E5E5E5] dark:border-[#1A1A1A]">
|
||||
<div className="flex items-center gap-4">
|
||||
{!authState.isConnected ? (
|
||||
<button
|
||||
type="submit"
|
||||
disabled={authState.isVerifying || !authState.username || !authState.tokenInfo?.token}
|
||||
className={classNames(
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-colors',
|
||||
'bg-purple-500 hover:bg-purple-600',
|
||||
'text-white',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
)}
|
||||
>
|
||||
{authState.isVerifying ? (
|
||||
<>
|
||||
<div className="i-ph:spinner animate-spin" />
|
||||
<span>Verifying...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="i-ph:plug-fill" />
|
||||
<span>Connect</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={onDisconnect}
|
||||
className={classNames(
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-colors',
|
||||
'bg-[#F5F5F5] hover:bg-red-500/10 hover:text-red-500',
|
||||
'dark:bg-[#1A1A1A] dark:hover:bg-red-500/20 dark:hover:text-red-500',
|
||||
'text-bolt-elements-textPrimary',
|
||||
)}
|
||||
>
|
||||
<div className="i-ph:plug-fill" />
|
||||
<span>Disconnect</span>
|
||||
</button>
|
||||
<span className="inline-flex items-center gap-2 px-3 py-1.5 text-sm text-green-600 dark:text-green-400 bg-green-500/5 rounded-lg border border-green-500/20">
|
||||
<div className="i-ph:check-circle-fill" />
|
||||
<span>Connected</span>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{authState.rateLimits && (
|
||||
<div className="flex items-center gap-2 text-sm text-bolt-elements-textTertiary">
|
||||
<div className="i-ph:clock-countdown opacity-60" />
|
||||
<span>Rate limit resets at {authState.rateLimits.reset.toLocaleTimeString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { useState } from 'react';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import type { GitHubRepoInfo } from '~/components/settings/connections/types/GitHub';
|
||||
import { GitBranch } from '@phosphor-icons/react';
|
||||
|
||||
interface GitHubBranch {
|
||||
name: string;
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
interface CreateBranchDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: (branchName: string, sourceBranch: string) => void;
|
||||
repository: GitHubRepoInfo;
|
||||
branches?: GitHubBranch[];
|
||||
}
|
||||
|
||||
export function CreateBranchDialog({ isOpen, onClose, onConfirm, repository, branches }: CreateBranchDialogProps) {
|
||||
const [branchName, setBranchName] = useState('');
|
||||
const [sourceBranch, setSourceBranch] = useState(branches?.find((b) => b.default)?.name || 'main');
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onConfirm(branchName, sourceBranch);
|
||||
setBranchName('');
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog.Root open={isOpen} onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black/50 dark:bg-black/80" />
|
||||
<Dialog.Content
|
||||
className={classNames(
|
||||
'fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]',
|
||||
'w-full max-w-md p-6 rounded-xl shadow-lg',
|
||||
'bg-white dark:bg-[#0A0A0A]',
|
||||
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||
)}
|
||||
>
|
||||
<Dialog.Title className="text-lg font-medium text-bolt-elements-textPrimary mb-4">
|
||||
Create New Branch
|
||||
</Dialog.Title>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="branchName" className="block text-sm font-medium text-bolt-elements-textSecondary mb-2">
|
||||
Branch Name
|
||||
</label>
|
||||
<input
|
||||
id="branchName"
|
||||
type="text"
|
||||
value={branchName}
|
||||
onChange={(e) => setBranchName(e.target.value)}
|
||||
placeholder="feature/my-new-branch"
|
||||
className={classNames(
|
||||
'w-full px-3 py-2 rounded-lg',
|
||||
'bg-[#F5F5F5] dark:bg-[#1A1A1A]',
|
||||
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||
'text-bolt-elements-textPrimary placeholder:text-bolt-elements-textTertiary',
|
||||
'focus:outline-none focus:ring-2 focus:ring-purple-500/50',
|
||||
)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="sourceBranch"
|
||||
className="block text-sm font-medium text-bolt-elements-textSecondary mb-2"
|
||||
>
|
||||
Source Branch
|
||||
</label>
|
||||
<select
|
||||
id="sourceBranch"
|
||||
value={sourceBranch}
|
||||
onChange={(e) => setSourceBranch(e.target.value)}
|
||||
className={classNames(
|
||||
'w-full px-3 py-2 rounded-lg',
|
||||
'bg-[#F5F5F5] dark:bg-[#1A1A1A]',
|
||||
'border border-[#E5E5E5] dark:border-[#1A1A1A]',
|
||||
'text-bolt-elements-textPrimary',
|
||||
'focus:outline-none focus:ring-2 focus:ring-purple-500/50',
|
||||
)}
|
||||
>
|
||||
{branches?.map((branch) => (
|
||||
<option key={branch.name} value={branch.name}>
|
||||
{branch.name} {branch.default ? '(default)' : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-[#F5F5F5] dark:bg-[#1A1A1A] rounded-lg">
|
||||
<h4 className="text-sm font-medium text-bolt-elements-textSecondary mb-2">Branch Overview</h4>
|
||||
<ul className="space-y-2 text-sm text-bolt-elements-textSecondary">
|
||||
<li className="flex items-center gap-2">
|
||||
<GitBranch className="text-lg" />
|
||||
Repository: {repository.name}
|
||||
</li>
|
||||
{branchName && (
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="i-ph:check-circle text-green-500" />
|
||||
New branch will be created as: {branchName}
|
||||
</li>
|
||||
)}
|
||||
<li className="flex items-center gap-2">
|
||||
<div className="i-ph:check-circle text-green-500" />
|
||||
Based on: {sourceBranch}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className={classNames(
|
||||
'px-4 py-2 rounded-lg text-sm font-medium',
|
||||
'text-bolt-elements-textPrimary',
|
||||
'bg-[#F5F5F5] dark:bg-[#1A1A1A]',
|
||||
'hover:bg-purple-500/10 hover:text-purple-500',
|
||||
'dark:hover:bg-purple-500/20 dark:hover:text-purple-500',
|
||||
'transition-colors',
|
||||
)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className={classNames(
|
||||
'px-4 py-2 rounded-lg text-sm font-medium',
|
||||
'text-white bg-purple-500',
|
||||
'hover:bg-purple-600',
|
||||
'transition-colors',
|
||||
)}
|
||||
>
|
||||
Create Branch
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
59
app/components/settings/connections/types/GitHub.ts
Normal file
59
app/components/settings/connections/types/GitHub.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export interface GitHubUserResponse {
|
||||
login: string;
|
||||
avatar_url: string;
|
||||
html_url: string;
|
||||
name: string;
|
||||
bio: string;
|
||||
public_repos: number;
|
||||
followers: number;
|
||||
following: number;
|
||||
}
|
||||
|
||||
export interface GitHubRepoInfo {
|
||||
name: string;
|
||||
full_name: string;
|
||||
html_url: string;
|
||||
description: string;
|
||||
stargazers_count: number;
|
||||
forks_count: number;
|
||||
default_branch: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface GitHubStats {
|
||||
repos: GitHubRepoInfo[];
|
||||
totalStars: number;
|
||||
totalForks: number;
|
||||
}
|
||||
|
||||
export interface GitHubConnection {
|
||||
user: GitHubUserResponse | null;
|
||||
token: string;
|
||||
tokenType: 'classic' | 'fine-grained';
|
||||
stats?: GitHubStats;
|
||||
}
|
||||
|
||||
export interface GitHubTokenInfo {
|
||||
token: string;
|
||||
scope: string[];
|
||||
avatar_url: string;
|
||||
name: string | null;
|
||||
created_at: string;
|
||||
followers: number;
|
||||
}
|
||||
|
||||
export interface GitHubRateLimits {
|
||||
limit: number;
|
||||
remaining: number;
|
||||
reset: Date;
|
||||
used: number;
|
||||
}
|
||||
|
||||
export interface GitHubAuthState {
|
||||
username: string;
|
||||
tokenInfo: GitHubTokenInfo | null;
|
||||
isConnected: boolean;
|
||||
isVerifying: boolean;
|
||||
isLoadingRepos: boolean;
|
||||
rateLimits?: GitHubRateLimits;
|
||||
}
|
||||
Reference in New Issue
Block a user