Stijnus commited on
Commit
f32016c
·
1 Parent(s): 387516b

Github enhancement

Browse files
.cursorrules CHANGED
@@ -8,8 +8,6 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo
8
  - Focus on best practices and clean code
9
  - Provide clear explanations for code changes
10
  - Maintain consistent code style with the existing codebase
11
- - Always write comments that are relevant to the code they describe
12
- - Always write a changelog what you did and save it in a file called changelog.md in the root of the project
13
 
14
  # Techstack
15
 
@@ -134,3 +132,25 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo
134
  4. Validate your solution against existing patterns
135
  5. Test thoroughly before considering work complete
136
  6. Review impact on related components
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  - Focus on best practices and clean code
9
  - Provide clear explanations for code changes
10
  - Maintain consistent code style with the existing codebase
 
 
11
 
12
  # Techstack
13
 
 
132
  4. Validate your solution against existing patterns
133
  5. Test thoroughly before considering work complete
134
  6. Review impact on related components
135
+
136
+ # UI GUIDELINES
137
+
138
+ - Use consistent colors and typography
139
+ - Ensure UI is responsive and accessible
140
+ - Provide clear feedback for user actions
141
+ - Use meaningful icons and labels
142
+ - Keep UI clean and organized
143
+ - Use consistent spacing and alignment
144
+ - Use consistent naming conventions for components and variables
145
+ - Use consistent file and folder structure
146
+ - Use consistent naming conventions for components and variables
147
+ - Use consistent file and folder structure
148
+
149
+ # Style Guide
150
+
151
+ - Use consistent naming conventions for components and variables
152
+ - Use consistent file and folder structure
153
+ - Respect the Light/Dark mode
154
+ - Don't use white background for dark mode
155
+ - Don't use white text on white background for dark mode
156
+ - Match the style of the existing codebase
app/components/settings/connections/components/PushToGitHubDialog.tsx CHANGED
@@ -6,11 +6,16 @@ import { getLocalStorage } from '~/utils/localStorage';
6
  import { classNames } from '~/utils/classNames';
7
  import type { GitHubUserResponse } from '~/types/GitHub';
8
  import { logStore } from '~/lib/stores/logs';
 
 
 
 
 
9
 
10
  interface PushToGitHubDialogProps {
11
  isOpen: boolean;
12
  onClose: () => void;
13
- onPush: (repoName: string, username?: string, token?: string) => Promise<void>;
14
  }
15
 
16
  interface GitHubRepo {
@@ -35,6 +40,7 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
35
  const [isFetchingRepos, setIsFetchingRepos] = useState(false);
36
  const [showSuccessDialog, setShowSuccessDialog] = useState(false);
37
  const [createdRepoUrl, setCreatedRepoUrl] = useState('');
 
38
 
39
  // Load GitHub connection on mount
40
  useEffect(() => {
@@ -126,10 +132,44 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
126
  setIsLoading(true);
127
 
128
  try {
129
- await onPush(repoName, connection.user.login, connection.token);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- const repoUrl = `https://github.com/${connection.user.login}/${repoName}`;
132
  setCreatedRepoUrl(repoUrl);
 
 
 
 
 
 
 
 
 
 
 
133
  setShowSuccessDialog(true);
134
  } catch (error) {
135
  console.error('Error pushing to GitHub:', error);
@@ -159,26 +199,23 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
159
  animate={{ opacity: 1, scale: 1 }}
160
  exit={{ opacity: 0, scale: 0.95 }}
161
  transition={{ duration: 0.2 }}
162
- className="w-[90vw] md:w-[500px]"
163
  >
164
- <Dialog.Content className="bg-white dark:bg-[#0A0A0A] rounded-lg p-6 border border-[#E5E5E5] dark:border-[#1A1A1A] shadow-xl">
165
- <div className="text-center space-y-4">
166
- <motion.div
167
- initial={{ scale: 0.8 }}
168
- animate={{ scale: 1 }}
169
- transition={{ delay: 0.1 }}
170
- className="mx-auto w-12 h-12 rounded-xl bg-green-500/10 flex items-center justify-center text-green-500"
171
- >
172
- <div className="i-ph:check-circle-bold w-6 h-6" />
173
- </motion.div>
174
- <div className="space-y-2">
175
- <h3 className="text-lg font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark">
176
- Repository Created Successfully!
177
- </h3>
178
- <p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark">
179
- Your code has been pushed to GitHub and is ready to use.
180
- </p>
181
  </div>
 
182
  <div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3 text-left">
183
  <p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
184
  Repository URL
@@ -200,26 +237,58 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial
200
  </motion.button>
201
  </div>
202
  </div>
203
- <div className="flex gap-2 pt-2">
204
- <motion.button
205
- onClick={handleClose}
206
- className="flex-1 px-4 py-2 text-sm bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700"
207
- whileHover={{ scale: 1.02 }}
208
- whileTap={{ scale: 0.98 }}
209
- >
210
- Close
211
- </motion.button>
 
 
 
 
 
 
 
 
 
 
 
 
212
  <motion.a
213
  href={createdRepoUrl}
214
  target="_blank"
215
  rel="noopener noreferrer"
216
- className="flex-1 px-4 py-2 text-sm bg-purple-500 text-white rounded-lg hover:bg-purple-600 inline-flex items-center justify-center gap-2"
217
  whileHover={{ scale: 1.02 }}
218
  whileTap={{ scale: 0.98 }}
219
  >
220
  <div className="i-ph:github-logo w-4 h-4" />
221
- Open Repository
222
  </motion.a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  </div>
224
  </div>
225
  </Dialog.Content>
 
6
  import { classNames } from '~/utils/classNames';
7
  import type { GitHubUserResponse } from '~/types/GitHub';
8
  import { logStore } from '~/lib/stores/logs';
9
+ import { workbenchStore } from '~/lib/stores/workbench';
10
+ import { extractRelativePath } from '~/utils/diff';
11
+ import { formatSize } from '~/utils/formatSize';
12
+ import type { FileMap, File } from '~/lib/stores/files';
13
+ import { Octokit } from '@octokit/rest';
14
 
15
  interface PushToGitHubDialogProps {
16
  isOpen: boolean;
17
  onClose: () => void;
18
+ onPush: (repoName: string, username?: string, token?: string, isPrivate?: boolean) => Promise<string>;
19
  }
20
 
21
  interface GitHubRepo {
 
40
  const [isFetchingRepos, setIsFetchingRepos] = useState(false);
41
  const [showSuccessDialog, setShowSuccessDialog] = useState(false);
42
  const [createdRepoUrl, setCreatedRepoUrl] = useState('');
43
+ const [pushedFiles, setPushedFiles] = useState<{ path: string; size: number }[]>([]);
44
 
45
  // Load GitHub connection on mount
46
  useEffect(() => {
 
132
  setIsLoading(true);
133
 
134
  try {
135
+ // Check if repository exists first
136
+ const octokit = new Octokit({ auth: connection.token });
137
+
138
+ try {
139
+ await octokit.repos.get({
140
+ owner: connection.user.login,
141
+ repo: repoName,
142
+ });
143
+
144
+ // If we get here, the repo exists
145
+ const confirmOverwrite = window.confirm(
146
+ `Repository "${repoName}" already exists. Do you want to update it? This will add or modify files in the repository.`,
147
+ );
148
+
149
+ if (!confirmOverwrite) {
150
+ setIsLoading(false);
151
+ return;
152
+ }
153
+ } catch (error) {
154
+ // 404 means repo doesn't exist, which is what we want for new repos
155
+ if (error instanceof Error && 'status' in error && error.status !== 404) {
156
+ throw error;
157
+ }
158
+ }
159
 
160
+ const repoUrl = await onPush(repoName, connection.user.login, connection.token, isPrivate);
161
  setCreatedRepoUrl(repoUrl);
162
+
163
+ // Get list of pushed files
164
+ const files = workbenchStore.files.get();
165
+ const filesList = Object.entries(files as FileMap)
166
+ .filter(([, dirent]) => dirent?.type === 'file' && !dirent.isBinary)
167
+ .map(([path, dirent]) => ({
168
+ path: extractRelativePath(path),
169
+ size: new TextEncoder().encode((dirent as File).content || '').length,
170
+ }));
171
+
172
+ setPushedFiles(filesList);
173
  setShowSuccessDialog(true);
174
  } catch (error) {
175
  console.error('Error pushing to GitHub:', error);
 
199
  animate={{ opacity: 1, scale: 1 }}
200
  exit={{ opacity: 0, scale: 0.95 }}
201
  transition={{ duration: 0.2 }}
202
+ className="w-[90vw] md:w-[600px] max-h-[85vh] overflow-y-auto"
203
  >
204
+ <Dialog.Content className="bg-white dark:bg-[#1E1E1E] rounded-lg border border-[#E5E5E5] dark:border-[#333333] shadow-xl">
205
+ <div className="p-6 space-y-4">
206
+ <div className="flex items-center justify-between">
207
+ <div className="flex items-center gap-2 text-green-500">
208
+ <div className="i-ph:check-circle w-5 h-5" />
209
+ <h3 className="text-lg font-medium">Successfully pushed to GitHub</h3>
210
+ </div>
211
+ <Dialog.Close
212
+ onClick={handleClose}
213
+ className="p-2 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-gray-400"
214
+ >
215
+ <div className="i-ph:x w-5 h-5" />
216
+ </Dialog.Close>
 
 
 
 
217
  </div>
218
+
219
  <div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3 text-left">
220
  <p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
221
  Repository URL
 
237
  </motion.button>
238
  </div>
239
  </div>
240
+
241
+ <div className="bg-bolt-elements-background-depth-2 dark:bg-bolt-elements-background-depth-3 rounded-lg p-3">
242
+ <p className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark mb-2">
243
+ Pushed Files ({pushedFiles.length})
244
+ </p>
245
+ <div className="max-h-[200px] overflow-y-auto custom-scrollbar">
246
+ {pushedFiles.map((file) => (
247
+ <div
248
+ key={file.path}
249
+ className="flex items-center justify-between py-1 text-sm text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary-dark"
250
+ >
251
+ <span className="font-mono truncate flex-1">{file.path}</span>
252
+ <span className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary-dark ml-2">
253
+ {formatSize(file.size)}
254
+ </span>
255
+ </div>
256
+ ))}
257
+ </div>
258
+ </div>
259
+
260
+ <div className="flex justify-end gap-2 pt-2">
261
  <motion.a
262
  href={createdRepoUrl}
263
  target="_blank"
264
  rel="noopener noreferrer"
265
+ className="px-4 py-2 rounded-lg bg-purple-500 text-white hover:bg-purple-600 text-sm inline-flex items-center gap-2"
266
  whileHover={{ scale: 1.02 }}
267
  whileTap={{ scale: 0.98 }}
268
  >
269
  <div className="i-ph:github-logo w-4 h-4" />
270
+ View Repository
271
  </motion.a>
272
+ <motion.button
273
+ onClick={() => {
274
+ navigator.clipboard.writeText(createdRepoUrl);
275
+ toast.success('URL copied to clipboard');
276
+ }}
277
+ className="px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] text-gray-600 dark:text-gray-400 hover:bg-[#E5E5E5] dark:hover:bg-[#252525] text-sm inline-flex items-center gap-2"
278
+ whileHover={{ scale: 1.02 }}
279
+ whileTap={{ scale: 0.98 }}
280
+ >
281
+ <div className="i-ph:copy w-4 h-4" />
282
+ Copy URL
283
+ </motion.button>
284
+ <motion.button
285
+ onClick={handleClose}
286
+ className="px-4 py-2 rounded-lg bg-[#F5F5F5] dark:bg-[#1A1A1A] text-gray-600 dark:text-gray-400 hover:bg-[#E5E5E5] dark:hover:bg-[#252525] text-sm"
287
+ whileHover={{ scale: 1.02 }}
288
+ whileTap={{ scale: 0.98 }}
289
+ >
290
+ Close
291
+ </motion.button>
292
  </div>
293
  </div>
294
  </Dialog.Content>
app/components/workbench/Workbench.client.tsx CHANGED
@@ -215,11 +215,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
215
  <PushToGitHubDialog
216
  isOpen={isPushDialogOpen}
217
  onClose={() => setIsPushDialogOpen(false)}
218
- onPush={async (repoName, username, token) => {
219
  try {
220
- await workbenchStore.pushToGitHub(repoName, undefined, username, token);
221
-
222
- // Success dialog will be shown by PushToGitHubDialog component
223
  } catch (error) {
224
  console.error('Error pushing to GitHub:', error);
225
  toast.error('Failed to push to GitHub');
 
215
  <PushToGitHubDialog
216
  isOpen={isPushDialogOpen}
217
  onClose={() => setIsPushDialogOpen(false)}
218
+ onPush={async (repoName, username, token, isPrivate) => {
219
  try {
220
+ const repoUrl = await workbenchStore.pushToGitHub(repoName, undefined, username, token, isPrivate);
221
+ return repoUrl;
 
222
  } catch (error) {
223
  console.error('Error pushing to GitHub:', error);
224
  toast.error('Failed to push to GitHub');
app/lib/stores/workbench.ts CHANGED
@@ -434,7 +434,13 @@ export class WorkbenchStore {
434
  return syncedFiles;
435
  }
436
 
437
- async pushToGitHub(repoName: string, commitMessage?: string, githubUsername?: string, ghToken?: string) {
 
 
 
 
 
 
438
  try {
439
  // Use cookies if username and token are not provided
440
  const githubToken = ghToken || Cookies.get('githubToken');
@@ -458,7 +464,7 @@ export class WorkbenchStore {
458
  // Repository doesn't exist, so create a new one
459
  const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({
460
  name: repoName,
461
- private: false,
462
  auto_init: true,
463
  });
464
  repo = newRepo;
 
434
  return syncedFiles;
435
  }
436
 
437
+ async pushToGitHub(
438
+ repoName: string,
439
+ commitMessage?: string,
440
+ githubUsername?: string,
441
+ ghToken?: string,
442
+ isPrivate: boolean = false,
443
+ ) {
444
  try {
445
  // Use cookies if username and token are not provided
446
  const githubToken = ghToken || Cookies.get('githubToken');
 
464
  // Repository doesn't exist, so create a new one
465
  const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({
466
  name: repoName,
467
+ private: isPrivate,
468
  auto_init: true,
469
  });
470
  repo = newRepo;