Skip to content

Commit 5fe38c9

Browse files
authored
Merge pull request #3 from D-Byte/iAlsoWantGitLab
fix: merge my own set
2 parents f740aa7 + 6a72194 commit 5fe38c9

21 files changed

+1367
-424
lines changed

app/components/chat/GitCloneButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import ignore from 'ignore';
2-
import { useGit } from '~/lib/hooks/useGit';
32
import type { Message } from 'ai';
43
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
54
import { generateId } from '~/utils/fileUtils';
5+
import { useGit } from '~/lib/git';
66

77
const IGNORE_PATTERNS = [
88
'node_modules/**',

app/components/git/GitUrlImport.client.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { useEffect, useState } from 'react';
55
import { ClientOnly } from 'remix-utils/client-only';
66
import { BaseChat } from '~/components/chat/BaseChat';
77
import { Chat } from '~/components/chat/Chat.client';
8-
import { useGit } from '~/lib/hooks/useGit';
98
import { useChatHistory } from '~/lib/persistence';
109
import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands';
1110
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
1211
import { toast } from 'react-toastify';
12+
import { useGit } from '~/lib/git';
1313

1414
const IGNORE_PATTERNS = [
1515
'node_modules/**',
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,36 @@
1-
import React, { useState, useEffect } from 'react';
2-
import { toast } from 'react-toastify';
3-
import Cookies from 'js-cookie';
4-
import { logStore } from '~/lib/stores/logs';
5-
6-
interface GitHubUserResponse {
7-
login: string;
8-
id: number;
9-
[key: string]: any; // for other properties we don't explicitly need
10-
}
1+
import React from 'react';
2+
import { ProviderCard } from '~/lib/git/components/ProviderCard';
3+
import { useGitProviders } from '~/lib/git/hooks/useGitProviders';
114

125
export default function ConnectionsTab() {
13-
const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
14-
const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
15-
const [isConnected, setIsConnected] = useState(false);
16-
const [isVerifying, setIsVerifying] = useState(false);
17-
18-
useEffect(() => {
19-
// Check if credentials exist and verify them
20-
if (githubUsername && githubToken) {
21-
verifyGitHubCredentials();
22-
}
23-
}, []);
24-
25-
const verifyGitHubCredentials = async () => {
26-
setIsVerifying(true);
27-
28-
try {
29-
const response = await fetch('https://api.github.com/user', {
30-
headers: {
31-
Authorization: `Bearer ${githubToken}`,
32-
},
33-
});
34-
35-
if (response.ok) {
36-
const data = (await response.json()) as GitHubUserResponse;
37-
38-
if (data.login === githubUsername) {
39-
setIsConnected(true);
40-
return true;
41-
}
42-
}
43-
44-
setIsConnected(false);
45-
46-
return false;
47-
} catch (error) {
48-
console.error('Error verifying GitHub credentials:', error);
49-
setIsConnected(false);
50-
51-
return false;
52-
} finally {
53-
setIsVerifying(false);
54-
}
55-
};
56-
57-
const handleSaveConnection = async () => {
58-
if (!githubUsername || !githubToken) {
59-
toast.error('Please provide both GitHub username and token');
60-
return;
61-
}
62-
63-
setIsVerifying(true);
64-
65-
const isValid = await verifyGitHubCredentials();
66-
67-
if (isValid) {
68-
Cookies.set('githubUsername', githubUsername);
69-
Cookies.set('githubToken', githubToken);
70-
logStore.logSystem('GitHub connection settings updated', {
71-
username: githubUsername,
72-
hasToken: !!githubToken,
73-
});
74-
toast.success('GitHub credentials verified and saved successfully!');
75-
Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' }));
76-
setIsConnected(true);
77-
} else {
78-
toast.error('Invalid GitHub credentials. Please check your username and token.');
79-
}
80-
};
81-
82-
const handleDisconnect = () => {
83-
Cookies.remove('githubUsername');
84-
Cookies.remove('githubToken');
85-
Cookies.remove('git:github.com');
86-
setGithubUsername('');
87-
setGithubToken('');
88-
setIsConnected(false);
89-
logStore.logSystem('GitHub connection removed');
90-
toast.success('GitHub connection removed successfully!');
91-
};
6+
const {
7+
providers,
8+
credentials,
9+
expandedProviders,
10+
handleSaveConnection,
11+
handleDisconnect,
12+
updateProviderCredentials,
13+
toggleProvider,
14+
} = useGitProviders();
9215

9316
return (
94-
<div className="p-4 mb-4 border border-bolt-elements-borderColor rounded-lg bg-bolt-elements-background-depth-3">
95-
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">GitHub Connection</h3>
96-
<div className="flex mb-4">
97-
<div className="flex-1 mr-2">
98-
<label className="block text-sm text-bolt-elements-textSecondary mb-1">GitHub Username:</label>
99-
<input
100-
type="text"
101-
value={githubUsername}
102-
onChange={(e) => setGithubUsername(e.target.value)}
103-
disabled={isVerifying}
104-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
105-
/>
106-
</div>
107-
<div className="flex-1">
108-
<label className="block text-sm text-bolt-elements-textSecondary mb-1">Personal Access Token:</label>
109-
<input
110-
type="password"
111-
value={githubToken}
112-
onChange={(e) => setGithubToken(e.target.value)}
113-
disabled={isVerifying}
114-
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
115-
/>
116-
</div>
117-
</div>
118-
<div className="flex mb-4 items-center">
119-
{!isConnected ? (
120-
<button
121-
onClick={handleSaveConnection}
122-
disabled={isVerifying || !githubUsername || !githubToken}
123-
className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
124-
>
125-
{isVerifying ? (
126-
<>
127-
<div className="i-ph:spinner animate-spin mr-2" />
128-
Verifying...
129-
</>
130-
) : (
131-
'Connect'
132-
)}
133-
</button>
134-
) : (
135-
<button
136-
onClick={handleDisconnect}
137-
className="bg-bolt-elements-button-danger-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text"
138-
>
139-
Disconnect
140-
</button>
141-
)}
142-
{isConnected && (
143-
<span className="text-sm text-green-600 flex items-center">
144-
<div className="i-ph:check-circle mr-1" />
145-
Connected to GitHub
146-
</span>
147-
)}
148-
</div>
17+
<div className="space-y-4">
18+
<div className="i-ph:github-logo-duotone hidden" />
19+
// Preloading icons otherwise they will not be displayed. Need fixing.
20+
<div className="i-ph:gitlab-logo-duotone hidden" />
21+
// Preloading icons otherwise they will not be displayed. Need fixing.
22+
{Object.entries(providers).map(([key, plugin]) => (
23+
<ProviderCard
24+
key={key}
25+
provider={plugin.provider}
26+
credentials={credentials[key]}
27+
isExpanded={expandedProviders[key]}
28+
onToggle={() => toggleProvider(key)}
29+
onUpdateCredentials={(updates) => updateProviderCredentials(key, updates)}
30+
onSave={() => handleSaveConnection(key)}
31+
onDisconnect={() => handleDisconnect(key)}
32+
/>
33+
))}
14934
</div>
15035
);
15136
}

app/components/workbench/Workbench.client.tsx

+47-36
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useStore } from '@nanostores/react';
22
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
33
import { computed } from 'nanostores';
4-
import { memo, useCallback, useEffect, useState } from 'react';
4+
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
55
import { toast } from 'react-toastify';
66
import {
77
type OnChangeCallback as OnEditorChange,
@@ -17,7 +17,7 @@ import { renderLogger } from '~/utils/logger';
1717
import { EditorPanel } from './EditorPanel';
1818
import { Preview } from './Preview';
1919
import useViewport from '~/lib/hooks';
20-
import Cookies from 'js-cookie';
20+
import { getGitCredentials, createGitPushHandler, gitProviders } from '~/lib/git';
2121

2222
interface WorkspaceProps {
2323
chatStarted?: boolean;
@@ -58,6 +58,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
5858
renderLogger.trace('Workbench');
5959

6060
const [isSyncing, setIsSyncing] = useState(false);
61+
const [hasCredentials, setHasCredentials] = useState<{ github: boolean; gitlab: boolean }>({
62+
github: false,
63+
gitlab: false,
64+
});
6165

6266
const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
6367
const showWorkbench = useStore(workbenchStore.showWorkbench);
@@ -83,6 +87,17 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
8387
workbenchStore.setDocuments(files);
8488
}, [files]);
8589

90+
useEffect(() => {
91+
const initCredentials = async () => {
92+
const credentials = await getGitCredentials();
93+
setHasCredentials({
94+
github: credentials.github || false,
95+
gitlab: credentials.gitlab || false,
96+
});
97+
};
98+
initCredentials();
99+
}, []);
100+
86101
const onEditorChange = useCallback<OnEditorChange>((update) => {
87102
workbenchStore.setCurrentDocumentContent(update.content);
88103
}, []);
@@ -120,6 +135,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
120135
}
121136
}, []);
122137

138+
const cleanPath = (path: string): string => {
139+
return path.replace('/home/project/', '');
140+
};
141+
142+
const getWorkbenchFiles = useCallback(() => {
143+
const docs = workbenchStore.files.get();
144+
return Object.entries(docs).reduce(
145+
(acc, [path, doc]) => {
146+
if (doc && 'content' in doc) {
147+
acc[cleanPath(path)] = (doc as { content: string }).content;
148+
}
149+
150+
return acc;
151+
},
152+
{} as Record<string, string>,
153+
);
154+
}, []);
155+
156+
const pushFunctions = useMemo(() => createGitPushHandler(getWorkbenchFiles), [getWorkbenchFiles]);
157+
123158
return (
124159
chatStarted && (
125160
<motion.div
@@ -168,40 +203,15 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
168203
<div className="i-ph:terminal" />
169204
Toggle Terminal
170205
</PanelHeaderButton>
171-
<PanelHeaderButton
172-
className="mr-1 text-sm"
173-
onClick={() => {
174-
const repoName = prompt(
175-
'Please enter a name for your new GitHub repository:',
176-
'bolt-generated-project',
177-
);
178-
179-
if (!repoName) {
180-
alert('Repository name is required. Push to GitHub cancelled.');
181-
return;
182-
}
183-
184-
const githubUsername = Cookies.get('githubUsername');
185-
const githubToken = Cookies.get('githubToken');
186-
187-
if (!githubUsername || !githubToken) {
188-
const usernameInput = prompt('Please enter your GitHub username:');
189-
const tokenInput = prompt('Please enter your GitHub personal access token:');
190-
191-
if (!usernameInput || !tokenInput) {
192-
alert('GitHub username and token are required. Push to GitHub cancelled.');
193-
return;
194-
}
195-
196-
workbenchStore.pushToGitHub(repoName, usernameInput, tokenInput);
197-
} else {
198-
workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
199-
}
200-
}}
201-
>
202-
<div className="i-ph:github-logo" />
203-
Push to GitHub
204-
</PanelHeaderButton>
206+
{Object.entries(gitProviders).map(
207+
([name, plugin]) =>
208+
hasCredentials[name as keyof typeof hasCredentials] && (
209+
<PanelHeaderButton key={name} className="mr-1 text-sm" onClick={pushFunctions[name]}>
210+
<div className={plugin.provider.icon} />
211+
Push to {plugin.provider.title}
212+
</PanelHeaderButton>
213+
),
214+
)}
205215
</div>
206216
)}
207217
<IconButton
@@ -245,6 +255,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
245255
)
246256
);
247257
});
258+
248259
interface ViewProps extends HTMLMotionProps<'div'> {
249260
children: JSX.Element;
250261
}

0 commit comments

Comments
 (0)