Refactor Settings, FileTree, GitPanel, Shell, and CodeEditor components (#402)

This commit is contained in:
Haileyesus
2026-02-25 19:07:07 +03:00
committed by GitHub
parent 23801e9cc1
commit 5e3a7b69d7
149 changed files with 11627 additions and 8453 deletions

View File

@@ -0,0 +1,109 @@
import { ExternalLink, Key, Plus, Trash2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../../../ui/button';
import { Input } from '../../../../../ui/input';
import type { ApiKeyItem } from '../types';
type ApiKeysSectionProps = {
apiKeys: ApiKeyItem[];
showNewKeyForm: boolean;
newKeyName: string;
onShowNewKeyFormChange: (value: boolean) => void;
onNewKeyNameChange: (value: string) => void;
onCreateApiKey: () => void;
onCancelCreateApiKey: () => void;
onToggleApiKey: (keyId: string, isActive: boolean) => void;
onDeleteApiKey: (keyId: string) => void;
};
export default function ApiKeysSection({
apiKeys,
showNewKeyForm,
newKeyName,
onShowNewKeyFormChange,
onNewKeyNameChange,
onCreateApiKey,
onCancelCreateApiKey,
onToggleApiKey,
onDeleteApiKey,
}: ApiKeysSectionProps) {
const { t } = useTranslation('settings');
return (
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Key className="h-5 w-5" />
<h3 className="text-lg font-semibold">{t('apiKeys.title')}</h3>
</div>
<Button size="sm" onClick={() => onShowNewKeyFormChange(!showNewKeyForm)}>
<Plus className="h-4 w-4 mr-1" />
{t('apiKeys.newButton')}
</Button>
</div>
<div className="mb-4">
<p className="text-sm text-muted-foreground mb-2">{t('apiKeys.description')}</p>
<a
href="/api-docs.html"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-primary hover:underline inline-flex items-center gap-1"
>
{t('apiKeys.apiDocsLink')}
<ExternalLink className="h-3 w-3" />
</a>
</div>
{showNewKeyForm && (
<div className="mb-4 p-4 border rounded-lg bg-card">
<Input
placeholder={t('apiKeys.form.placeholder')}
value={newKeyName}
onChange={(event) => onNewKeyNameChange(event.target.value)}
className="mb-2"
/>
<div className="flex gap-2">
<Button onClick={onCreateApiKey}>{t('apiKeys.form.createButton')}</Button>
<Button variant="outline" onClick={onCancelCreateApiKey}>
{t('apiKeys.form.cancelButton')}
</Button>
</div>
</div>
)}
<div className="space-y-2">
{apiKeys.length === 0 ? (
<p className="text-sm text-muted-foreground italic">{t('apiKeys.empty')}</p>
) : (
apiKeys.map((key) => (
<div key={key.id} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex-1">
<div className="font-medium">{key.key_name}</div>
<code className="text-xs text-muted-foreground">{key.api_key}</code>
<div className="text-xs text-muted-foreground mt-1">
{t('apiKeys.list.created')} {new Date(key.created_at).toLocaleDateString()}
{key.last_used
? ` - ${t('apiKeys.list.lastUsed')} ${new Date(key.last_used).toLocaleDateString()}`
: ''}
</div>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant={key.is_active ? 'outline' : 'secondary'}
onClick={() => onToggleApiKey(key.id, key.is_active)}
>
{key.is_active ? t('apiKeys.status.active') : t('apiKeys.status.inactive')}
</Button>
<Button size="sm" variant="ghost" onClick={() => onDeleteApiKey(key.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,142 @@
import { Eye, EyeOff, Github, Plus, Trash2 } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../../../ui/button';
import { Input } from '../../../../../ui/input';
import type { GithubCredentialItem } from '../types';
type GithubCredentialsSectionProps = {
githubCredentials: GithubCredentialItem[];
showNewGithubForm: boolean;
showNewTokenPlainText: boolean;
newGithubName: string;
newGithubToken: string;
newGithubDescription: string;
onShowNewGithubFormChange: (value: boolean) => void;
onNewGithubNameChange: (value: string) => void;
onNewGithubTokenChange: (value: string) => void;
onNewGithubDescriptionChange: (value: string) => void;
onToggleNewTokenVisibility: () => void;
onCreateGithubCredential: () => void;
onCancelCreateGithubCredential: () => void;
onToggleGithubCredential: (credentialId: string, isActive: boolean) => void;
onDeleteGithubCredential: (credentialId: string) => void;
};
export default function GithubCredentialsSection({
githubCredentials,
showNewGithubForm,
showNewTokenPlainText,
newGithubName,
newGithubToken,
newGithubDescription,
onShowNewGithubFormChange,
onNewGithubNameChange,
onNewGithubTokenChange,
onNewGithubDescriptionChange,
onToggleNewTokenVisibility,
onCreateGithubCredential,
onCancelCreateGithubCredential,
onToggleGithubCredential,
onDeleteGithubCredential,
}: GithubCredentialsSectionProps) {
const { t } = useTranslation('settings');
return (
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Github className="h-5 w-5" />
<h3 className="text-lg font-semibold">{t('apiKeys.github.title')}</h3>
</div>
<Button size="sm" onClick={() => onShowNewGithubFormChange(!showNewGithubForm)}>
<Plus className="h-4 w-4 mr-1" />
{t('apiKeys.github.addButton')}
</Button>
</div>
<p className="text-sm text-muted-foreground mb-4">{t('apiKeys.github.descriptionAlt')}</p>
{showNewGithubForm && (
<div className="mb-4 p-4 border rounded-lg bg-card space-y-3">
<Input
placeholder={t('apiKeys.github.form.namePlaceholder')}
value={newGithubName}
onChange={(event) => onNewGithubNameChange(event.target.value)}
/>
<div className="relative">
<Input
type={showNewTokenPlainText ? 'text' : 'password'}
placeholder={t('apiKeys.github.form.tokenPlaceholder')}
value={newGithubToken}
onChange={(event) => onNewGithubTokenChange(event.target.value)}
className="pr-10"
/>
<button
type="button"
onClick={onToggleNewTokenVisibility}
aria-label={showNewTokenPlainText ? 'Hide token' : 'Show token'}
className="absolute right-3 top-2.5 text-muted-foreground hover:text-foreground"
>
{showNewTokenPlainText ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
<Input
placeholder={t('apiKeys.github.form.descriptionPlaceholder')}
value={newGithubDescription}
onChange={(event) => onNewGithubDescriptionChange(event.target.value)}
/>
<div className="flex gap-2">
<Button onClick={onCreateGithubCredential}>{t('apiKeys.github.form.addButton')}</Button>
<Button variant="outline" onClick={onCancelCreateGithubCredential}>
{t('apiKeys.github.form.cancelButton')}
</Button>
</div>
<a
href="https://github.com/settings/tokens"
target="_blank"
rel="noopener noreferrer"
className="text-xs text-primary hover:underline block"
>
{t('apiKeys.github.form.howToCreate')}
</a>
</div>
)}
<div className="space-y-2">
{githubCredentials.length === 0 ? (
<p className="text-sm text-muted-foreground italic">{t('apiKeys.github.empty')}</p>
) : (
githubCredentials.map((credential) => (
<div key={credential.id} className="flex items-center justify-between p-3 border rounded-lg">
<div className="flex-1">
<div className="font-medium">{credential.credential_name}</div>
{credential.description && (
<div className="text-xs text-muted-foreground">{credential.description}</div>
)}
<div className="text-xs text-muted-foreground mt-1">
{t('apiKeys.github.added')} {new Date(credential.created_at).toLocaleDateString()}
</div>
</div>
<div className="flex items-center gap-2">
<Button
size="sm"
variant={credential.is_active ? 'outline' : 'secondary'}
onClick={() => onToggleGithubCredential(credential.id, credential.is_active)}
>
{credential.is_active ? t('apiKeys.status.active') : t('apiKeys.status.inactive')}
</Button>
<Button size="sm" variant="ghost" onClick={() => onDeleteGithubCredential(credential.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,42 @@
import { Check, Copy } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../../../ui/button';
import type { CreatedApiKey } from '../types';
type NewApiKeyAlertProps = {
apiKey: CreatedApiKey;
copiedKey: string | null;
onCopy: (text: string, id: string) => void;
onDismiss: () => void;
};
export default function NewApiKeyAlert({
apiKey,
copiedKey,
onCopy,
onDismiss,
}: NewApiKeyAlertProps) {
const { t } = useTranslation('settings');
return (
<div className="p-4 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
<h4 className="font-semibold text-yellow-500 mb-2">{t('apiKeys.newKey.alertTitle')}</h4>
<p className="text-sm text-muted-foreground mb-3">{t('apiKeys.newKey.alertMessage')}</p>
<div className="flex items-center gap-2">
<code className="flex-1 px-3 py-2 bg-background/50 rounded font-mono text-sm break-all">
{apiKey.apiKey}
</code>
<Button
size="sm"
variant="outline"
onClick={() => onCopy(apiKey.apiKey, 'new')}
>
{copiedKey === 'new' ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</div>
<Button size="sm" variant="ghost" className="mt-3" onClick={onDismiss}>
{t('apiKeys.newKey.iveSavedIt')}
</Button>
</div>
);
}

View File

@@ -0,0 +1,46 @@
import { ExternalLink } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import type { ReleaseInfo } from '../../../../../../types/sharedTypes';
type VersionInfoSectionProps = {
currentVersion: string;
updateAvailable: boolean;
latestVersion: string | null;
releaseInfo: ReleaseInfo | null;
};
export default function VersionInfoSection({
currentVersion,
updateAvailable,
latestVersion,
releaseInfo,
}: VersionInfoSectionProps) {
const { t } = useTranslation('settings');
const releasesUrl = releaseInfo?.htmlUrl || 'https://github.com/siteboon/claudecodeui/releases';
return (
<div className="pt-6 border-t border-border/50">
<div className="flex items-center justify-between text-xs italic text-muted-foreground/60">
<a
href={releasesUrl}
target="_blank"
rel="noopener noreferrer"
className="hover:text-muted-foreground transition-colors"
>
v{currentVersion}
</a>
{updateAvailable && latestVersion && (
<a
href={releasesUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 px-2 py-0.5 bg-green-500/10 text-green-600 dark:text-green-400 rounded-full hover:bg-green-500/20 transition-colors not-italic font-medium"
>
<span className="text-[10px]">{t('apiKeys.version.updateAvailable', { version: latestVersion })}</span>
<ExternalLink className="h-2.5 w-2.5" />
</a>
)}
</div>
</div>
);
}