Skip to content

Commit

Permalink
Merge pull request #3 from D-Byte/iAlsoWantGitLab
Browse files Browse the repository at this point in the history
fix: merge my own set
  • Loading branch information
D-Byte authored Dec 22, 2024
2 parents f740aa7 + 6a72194 commit 5fe38c9
Show file tree
Hide file tree
Showing 21 changed files with 1,367 additions and 424 deletions.
2 changes: 1 addition & 1 deletion app/components/chat/GitCloneButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import ignore from 'ignore';
import { useGit } from '~/lib/hooks/useGit';
import type { Message } from 'ai';
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
import { generateId } from '~/utils/fileUtils';
import { useGit } from '~/lib/git';

const IGNORE_PATTERNS = [
'node_modules/**',
Expand Down
2 changes: 1 addition & 1 deletion app/components/git/GitUrlImport.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { BaseChat } from '~/components/chat/BaseChat';
import { Chat } from '~/components/chat/Chat.client';
import { useGit } from '~/lib/hooks/useGit';
import { useChatHistory } from '~/lib/persistence';
import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
import { toast } from 'react-toastify';
import { useGit } from '~/lib/git';

const IGNORE_PATTERNS = [
'node_modules/**',
Expand Down
173 changes: 29 additions & 144 deletions app/components/settings/connections/ConnectionsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,151 +1,36 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import Cookies from 'js-cookie';
import { logStore } from '~/lib/stores/logs';

interface GitHubUserResponse {
login: string;
id: number;
[key: string]: any; // for other properties we don't explicitly need
}
import React from 'react';
import { ProviderCard } from '~/lib/git/components/ProviderCard';
import { useGitProviders } from '~/lib/git/hooks/useGitProviders';

export default function ConnectionsTab() {
const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
const [isConnected, setIsConnected] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);

useEffect(() => {
// Check if credentials exist and verify them
if (githubUsername && githubToken) {
verifyGitHubCredentials();
}
}, []);

const verifyGitHubCredentials = async () => {
setIsVerifying(true);

try {
const response = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${githubToken}`,
},
});

if (response.ok) {
const data = (await response.json()) as GitHubUserResponse;

if (data.login === githubUsername) {
setIsConnected(true);
return true;
}
}

setIsConnected(false);

return false;
} catch (error) {
console.error('Error verifying GitHub credentials:', error);
setIsConnected(false);

return false;
} finally {
setIsVerifying(false);
}
};

const handleSaveConnection = async () => {
if (!githubUsername || !githubToken) {
toast.error('Please provide both GitHub username and token');
return;
}

setIsVerifying(true);

const isValid = await verifyGitHubCredentials();

if (isValid) {
Cookies.set('githubUsername', githubUsername);
Cookies.set('githubToken', githubToken);
logStore.logSystem('GitHub connection settings updated', {
username: githubUsername,
hasToken: !!githubToken,
});
toast.success('GitHub credentials verified and saved successfully!');
Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' }));
setIsConnected(true);
} else {
toast.error('Invalid GitHub credentials. Please check your username and token.');
}
};

const handleDisconnect = () => {
Cookies.remove('githubUsername');
Cookies.remove('githubToken');
Cookies.remove('git:github.com');
setGithubUsername('');
setGithubToken('');
setIsConnected(false);
logStore.logSystem('GitHub connection removed');
toast.success('GitHub connection removed successfully!');
};
const {
providers,
credentials,
expandedProviders,
handleSaveConnection,
handleDisconnect,
updateProviderCredentials,
toggleProvider,
} = useGitProviders();

return (
<div className="p-4 mb-4 border border-bolt-elements-borderColor rounded-lg bg-bolt-elements-background-depth-3">
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">GitHub Connection</h3>
<div className="flex mb-4">
<div className="flex-1 mr-2">
<label className="block text-sm text-bolt-elements-textSecondary mb-1">GitHub Username:</label>
<input
type="text"
value={githubUsername}
onChange={(e) => setGithubUsername(e.target.value)}
disabled={isVerifying}
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"
/>
</div>
<div className="flex-1">
<label className="block text-sm text-bolt-elements-textSecondary mb-1">Personal Access Token:</label>
<input
type="password"
value={githubToken}
onChange={(e) => setGithubToken(e.target.value)}
disabled={isVerifying}
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"
/>
</div>
</div>
<div className="flex mb-4 items-center">
{!isConnected ? (
<button
onClick={handleSaveConnection}
disabled={isVerifying || !githubUsername || !githubToken}
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"
>
{isVerifying ? (
<>
<div className="i-ph:spinner animate-spin mr-2" />
Verifying...
</>
) : (
'Connect'
)}
</button>
) : (
<button
onClick={handleDisconnect}
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"
>
Disconnect
</button>
)}
{isConnected && (
<span className="text-sm text-green-600 flex items-center">
<div className="i-ph:check-circle mr-1" />
Connected to GitHub
</span>
)}
</div>
<div className="space-y-4">
<div className="i-ph:github-logo-duotone hidden" />
// Preloading icons otherwise they will not be displayed. Need fixing.
<div className="i-ph:gitlab-logo-duotone hidden" />
// Preloading icons otherwise they will not be displayed. Need fixing.
{Object.entries(providers).map(([key, plugin]) => (
<ProviderCard
key={key}
provider={plugin.provider}
credentials={credentials[key]}
isExpanded={expandedProviders[key]}
onToggle={() => toggleProvider(key)}
onUpdateCredentials={(updates) => updateProviderCredentials(key, updates)}
onSave={() => handleSaveConnection(key)}
onDisconnect={() => handleDisconnect(key)}
/>
))}
</div>
);
}
83 changes: 47 additions & 36 deletions app/components/workbench/Workbench.client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useStore } from '@nanostores/react';
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
import { computed } from 'nanostores';
import { memo, useCallback, useEffect, useState } from 'react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import {
type OnChangeCallback as OnEditorChange,
Expand All @@ -17,7 +17,7 @@ import { renderLogger } from '~/utils/logger';
import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview';
import useViewport from '~/lib/hooks';
import Cookies from 'js-cookie';
import { getGitCredentials, createGitPushHandler, gitProviders } from '~/lib/git';

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

const [isSyncing, setIsSyncing] = useState(false);
const [hasCredentials, setHasCredentials] = useState<{ github: boolean; gitlab: boolean }>({
github: false,
gitlab: false,
});

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

useEffect(() => {
const initCredentials = async () => {
const credentials = await getGitCredentials();
setHasCredentials({
github: credentials.github || false,
gitlab: credentials.gitlab || false,
});
};
initCredentials();
}, []);

const onEditorChange = useCallback<OnEditorChange>((update) => {
workbenchStore.setCurrentDocumentContent(update.content);
}, []);
Expand Down Expand Up @@ -120,6 +135,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
}
}, []);

const cleanPath = (path: string): string => {
return path.replace('/home/project/', '');
};

const getWorkbenchFiles = useCallback(() => {
const docs = workbenchStore.files.get();
return Object.entries(docs).reduce(
(acc, [path, doc]) => {
if (doc && 'content' in doc) {
acc[cleanPath(path)] = (doc as { content: string }).content;
}

return acc;
},
{} as Record<string, string>,
);
}, []);

const pushFunctions = useMemo(() => createGitPushHandler(getWorkbenchFiles), [getWorkbenchFiles]);

return (
chatStarted && (
<motion.div
Expand Down Expand Up @@ -168,40 +203,15 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
<div className="i-ph:terminal" />
Toggle Terminal
</PanelHeaderButton>
<PanelHeaderButton
className="mr-1 text-sm"
onClick={() => {
const repoName = prompt(
'Please enter a name for your new GitHub repository:',
'bolt-generated-project',
);

if (!repoName) {
alert('Repository name is required. Push to GitHub cancelled.');
return;
}

const githubUsername = Cookies.get('githubUsername');
const githubToken = Cookies.get('githubToken');

if (!githubUsername || !githubToken) {
const usernameInput = prompt('Please enter your GitHub username:');
const tokenInput = prompt('Please enter your GitHub personal access token:');

if (!usernameInput || !tokenInput) {
alert('GitHub username and token are required. Push to GitHub cancelled.');
return;
}

workbenchStore.pushToGitHub(repoName, usernameInput, tokenInput);
} else {
workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
}
}}
>
<div className="i-ph:github-logo" />
Push to GitHub
</PanelHeaderButton>
{Object.entries(gitProviders).map(
([name, plugin]) =>
hasCredentials[name as keyof typeof hasCredentials] && (
<PanelHeaderButton key={name} className="mr-1 text-sm" onClick={pushFunctions[name]}>
<div className={plugin.provider.icon} />
Push to {plugin.provider.title}
</PanelHeaderButton>
),
)}
</div>
)}
<IconButton
Expand Down Expand Up @@ -245,6 +255,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
)
);
});

interface ViewProps extends HTMLMotionProps<'div'> {
children: JSX.Element;
}
Expand Down
Loading

0 comments on commit 5fe38c9

Please sign in to comment.