diff --git a/.cursorrules b/.cursorrules index 83b6c3da..3a57535c 100644 --- a/.cursorrules +++ b/.cursorrules @@ -8,8 +8,6 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo - Focus on best practices and clean code - Provide clear explanations for code changes - Maintain consistent code style with the existing codebase -- Always write comments that are relevant to the code they describe -- Always write a changelog what you did and save it in a file called changelog.md in the root of the project # Techstack @@ -134,3 +132,25 @@ bolt.diy (previously oTToDev) is an open-source AI-powered full-stack web develo 4. Validate your solution against existing patterns 5. Test thoroughly before considering work complete 6. Review impact on related components + +# UI GUIDELINES + +- Use consistent colors and typography +- Ensure UI is responsive and accessible +- Provide clear feedback for user actions +- Use meaningful icons and labels +- Keep UI clean and organized +- Use consistent spacing and alignment +- Use consistent naming conventions for components and variables +- Use consistent file and folder structure +- Use consistent naming conventions for components and variables +- Use consistent file and folder structure + +# Style Guide + +- Use consistent naming conventions for components and variables +- Use consistent file and folder structure +- Respect the Light/Dark mode +- Don't use white background for dark mode +- Don't use white text on white background for dark mode +- Match the style of the existing codebase diff --git a/app/components/settings/connections/components/PushToGitHubDialog.tsx b/app/components/settings/connections/components/PushToGitHubDialog.tsx index 14bbfbf6..fb4c6520 100644 --- a/app/components/settings/connections/components/PushToGitHubDialog.tsx +++ b/app/components/settings/connections/components/PushToGitHubDialog.tsx @@ -6,11 +6,16 @@ import { getLocalStorage } from '~/utils/localStorage'; import { classNames } from '~/utils/classNames'; import type { GitHubUserResponse } from '~/types/GitHub'; import { logStore } from '~/lib/stores/logs'; +import { workbenchStore } from '~/lib/stores/workbench'; +import { extractRelativePath } from '~/utils/diff'; +import { formatSize } from '~/utils/formatSize'; +import type { FileMap, File } from '~/lib/stores/files'; +import { Octokit } from '@octokit/rest'; interface PushToGitHubDialogProps { isOpen: boolean; onClose: () => void; - onPush: (repoName: string, username?: string, token?: string) => Promise; + onPush: (repoName: string, username?: string, token?: string, isPrivate?: boolean) => Promise; } interface GitHubRepo { @@ -35,6 +40,7 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial const [isFetchingRepos, setIsFetchingRepos] = useState(false); const [showSuccessDialog, setShowSuccessDialog] = useState(false); const [createdRepoUrl, setCreatedRepoUrl] = useState(''); + const [pushedFiles, setPushedFiles] = useState<{ path: string; size: number }[]>([]); // Load GitHub connection on mount useEffect(() => { @@ -126,10 +132,44 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial setIsLoading(true); try { - await onPush(repoName, connection.user.login, connection.token); + // Check if repository exists first + const octokit = new Octokit({ auth: connection.token }); - const repoUrl = `https://github.com/${connection.user.login}/${repoName}`; + try { + await octokit.repos.get({ + owner: connection.user.login, + repo: repoName, + }); + + // If we get here, the repo exists + const confirmOverwrite = window.confirm( + `Repository "${repoName}" already exists. Do you want to update it? This will add or modify files in the repository.`, + ); + + if (!confirmOverwrite) { + setIsLoading(false); + return; + } + } catch (error) { + // 404 means repo doesn't exist, which is what we want for new repos + if (error instanceof Error && 'status' in error && error.status !== 404) { + throw error; + } + } + + const repoUrl = await onPush(repoName, connection.user.login, connection.token, isPrivate); setCreatedRepoUrl(repoUrl); + + // Get list of pushed files + const files = workbenchStore.files.get(); + const filesList = Object.entries(files as FileMap) + .filter(([, dirent]) => dirent?.type === 'file' && !dirent.isBinary) + .map(([path, dirent]) => ({ + path: extractRelativePath(path), + size: new TextEncoder().encode((dirent as File).content || '').length, + })); + + setPushedFiles(filesList); setShowSuccessDialog(true); } catch (error) { console.error('Error pushing to GitHub:', error); @@ -159,26 +199,23 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.95 }} transition={{ duration: 0.2 }} - className="w-[90vw] md:w-[500px]" + className="w-[90vw] md:w-[600px] max-h-[85vh] overflow-y-auto" > - -
- -
- -
-

- Repository Created Successfully! -

-

- Your code has been pushed to GitHub and is ready to use. -

+ +
+
+
+
+

Successfully pushed to GitHub

+
+ +
+
+

Repository URL @@ -200,26 +237,58 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial

-
+ +
+

+ Pushed Files ({pushedFiles.length}) +

+
+ {pushedFiles.map((file) => ( +
+ {file.path} + + {formatSize(file.size)} + +
+ ))} +
+
+ +
+ +
+ View Repository + + { + navigator.clipboard.writeText(createdRepoUrl); + toast.success('URL copied to clipboard'); + }} + 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" + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > +
+ Copy URL + Close - -
- Open Repository -
diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx index e65c0b2c..b3e6aaba 100644 --- a/app/components/workbench/Workbench.client.tsx +++ b/app/components/workbench/Workbench.client.tsx @@ -215,11 +215,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) => setIsPushDialogOpen(false)} - onPush={async (repoName, username, token) => { + onPush={async (repoName, username, token, isPrivate) => { try { - await workbenchStore.pushToGitHub(repoName, undefined, username, token); - - // Success dialog will be shown by PushToGitHubDialog component + const repoUrl = await workbenchStore.pushToGitHub(repoName, undefined, username, token, isPrivate); + return repoUrl; } catch (error) { console.error('Error pushing to GitHub:', error); toast.error('Failed to push to GitHub'); diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 9c216094..16f48f24 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -434,7 +434,13 @@ export class WorkbenchStore { return syncedFiles; } - async pushToGitHub(repoName: string, commitMessage?: string, githubUsername?: string, ghToken?: string) { + async pushToGitHub( + repoName: string, + commitMessage?: string, + githubUsername?: string, + ghToken?: string, + isPrivate: boolean = false, + ) { try { // Use cookies if username and token are not provided const githubToken = ghToken || Cookies.get('githubToken'); @@ -458,7 +464,7 @@ export class WorkbenchStore { // Repository doesn't exist, so create a new one const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({ name: repoName, - private: false, + private: isPrivate, auto_init: true, }); repo = newRepo;