refactor(settings): remove empty hooks directory

This commit is contained in:
Kacper
2025-12-11 00:24:36 +01:00
parent da78bed47d
commit 9af6866a9d
8 changed files with 847 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Palette } from "lucide-react";
import { themeOptions } from "./shared/theme-options";
import type { Theme, Project } from "./types";
interface AppearanceSectionProps {
effectiveTheme: Theme;
currentProject: Project | null;
onThemeChange: (theme: Theme) => void;
}
export function AppearanceSection({
effectiveTheme,
currentProject,
onThemeChange,
}: AppearanceSectionProps) {
return (
<div
id="appearance"
className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-border">
<div className="flex items-center gap-2 mb-2">
<Palette className="w-5 h-5 text-brand-500" />
<h2 className="text-lg font-semibold text-foreground">Appearance</h2>
</div>
<p className="text-sm text-muted-foreground">
Customize the look and feel of your application.
</p>
</div>
<div className="p-6 space-y-4">
<div className="space-y-3">
<Label className="text-foreground">
Theme{" "}
{currentProject ? `(for ${currentProject.name})` : "(Global)"}
</Label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{themeOptions.map(({ value, label, Icon, testId }) => {
const isActive = effectiveTheme === value;
return (
<Button
key={value}
variant={isActive ? "secondary" : "outline"}
onClick={() => onThemeChange(value)}
className={`flex items-center justify-center gap-2 px-3 py-3 h-auto ${
isActive ? "border-brand-500 ring-1 ring-brand-500/50" : ""
}`}
data-testid={testId}
>
<Icon className="w-4 h-4" />
<span className="font-medium text-sm">{label}</span>
</Button>
);
})}
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,304 @@
import { Button } from "@/components/ui/button";
import {
Terminal,
CheckCircle2,
AlertCircle,
RefreshCw,
Atom,
} from "lucide-react";
import type { CliStatus } from "./types";
interface CliStatusProps {
status: CliStatus | null;
isChecking: boolean;
onRefresh: () => void;
}
export function ClaudeCliStatus({
status,
isChecking,
onRefresh,
}: CliStatusProps) {
if (!status) return null;
return (
<div
id="claude"
className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-border">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Terminal className="w-5 h-5 text-brand-500" />
<h2 className="text-lg font-semibold text-foreground">
Claude Code CLI
</h2>
</div>
<Button
variant="ghost"
size="icon"
onClick={onRefresh}
disabled={isChecking}
data-testid="refresh-claude-cli"
title="Refresh Claude CLI detection"
>
<RefreshCw
className={`w-4 h-4 ${isChecking ? "animate-spin" : ""}`}
/>
</Button>
</div>
<p className="text-sm text-muted-foreground">
Claude Code CLI provides better performance for long-running tasks,
especially with ultrathink.
</p>
</div>
<div className="p-6 space-y-4">
{status.success && status.status === "installed" ? (
<div className="space-y-3">
<div className="flex items-center gap-2 p-3 rounded-lg bg-green-500/10 border border-green-500/20">
<CheckCircle2 className="w-5 h-5 text-green-500 shrink-0" />
<div className="flex-1">
<p className="text-sm font-medium text-green-400">
Claude Code CLI Installed
</p>
<div className="text-xs text-green-400/80 mt-1 space-y-1">
{status.method && (
<p>
Method: <span className="font-mono">{status.method}</span>
</p>
)}
{status.version && (
<p>
Version:{" "}
<span className="font-mono">{status.version}</span>
</p>
)}
{status.path && (
<p className="truncate" title={status.path}>
Path:{" "}
<span className="font-mono text-[10px]">
{status.path}
</span>
</p>
)}
</div>
</div>
</div>
{status.recommendation && (
<p className="text-xs text-muted-foreground">
{status.recommendation}
</p>
)}
</div>
) : (
<div className="space-y-3">
<div className="flex items-start gap-3 p-3 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<AlertCircle className="w-5 h-5 text-yellow-500 mt-0.5 shrink-0" />
<div className="flex-1">
<p className="text-sm font-medium text-yellow-400">
Claude Code CLI Not Detected
</p>
<p className="text-xs text-yellow-400/80 mt-1">
{status.recommendation ||
"Consider installing Claude Code CLI for optimal performance with ultrathink."}
</p>
</div>
</div>
{status.installCommands && (
<div className="space-y-2">
<p className="text-xs font-medium text-foreground-secondary">
Installation Commands:
</p>
<div className="space-y-1">
{status.installCommands.npm && (
<div className="p-2 rounded bg-background border border-border-glass">
<p className="text-xs text-muted-foreground mb-1">npm:</p>
<code className="text-xs text-foreground-secondary font-mono break-all">
{status.installCommands.npm}
</code>
</div>
)}
{status.installCommands.macos && (
<div className="p-2 rounded bg-background border border-border-glass">
<p className="text-xs text-muted-foreground mb-1">
macOS/Linux:
</p>
<code className="text-xs text-foreground-secondary font-mono break-all">
{status.installCommands.macos}
</code>
</div>
)}
{status.installCommands.windows && (
<div className="p-2 rounded bg-background border border-border-glass">
<p className="text-xs text-muted-foreground mb-1">
Windows (PowerShell):
</p>
<code className="text-xs text-foreground-secondary font-mono break-all">
{status.installCommands.windows}
</code>
</div>
)}
</div>
</div>
)}
</div>
)}
</div>
</div>
);
}
export function CodexCliStatus({
status,
isChecking,
onRefresh,
}: CliStatusProps) {
if (!status) return null;
return (
<div
id="codex"
className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-border">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Terminal className="w-5 h-5 text-green-500" />
<h2 className="text-lg font-semibold text-foreground">
OpenAI Codex CLI
</h2>
</div>
<Button
variant="ghost"
size="icon"
onClick={onRefresh}
disabled={isChecking}
data-testid="refresh-codex-cli"
title="Refresh Codex CLI detection"
>
<RefreshCw
className={`w-4 h-4 ${isChecking ? "animate-spin" : ""}`}
/>
</Button>
</div>
<p className="text-sm text-muted-foreground">
Codex CLI enables GPT-5.1 Codex models for autonomous coding tasks.
</p>
</div>
<div className="p-6 space-y-4">
{status.success && status.status === "installed" ? (
<div className="space-y-3">
<div className="flex items-center gap-2 p-3 rounded-lg bg-green-500/10 border border-green-500/20">
<CheckCircle2 className="w-5 h-5 text-green-500 shrink-0" />
<div className="flex-1">
<p className="text-sm font-medium text-green-400">
Codex CLI Installed
</p>
<div className="text-xs text-green-400/80 mt-1 space-y-1">
{status.method && (
<p>
Method: <span className="font-mono">{status.method}</span>
</p>
)}
{status.version && (
<p>
Version:{" "}
<span className="font-mono">{status.version}</span>
</p>
)}
{status.path && (
<p className="truncate" title={status.path}>
Path:{" "}
<span className="font-mono text-[10px]">
{status.path}
</span>
</p>
)}
</div>
</div>
</div>
{status.recommendation && (
<p className="text-xs text-muted-foreground">
{status.recommendation}
</p>
)}
</div>
) : status.status === "api_key_only" ? (
<div className="space-y-3">
<div className="flex items-start gap-3 p-3 rounded-lg bg-blue-500/10 border border-blue-500/20">
<AlertCircle className="w-5 h-5 text-blue-500 mt-0.5 shrink-0" />
<div className="flex-1">
<p className="text-sm font-medium text-blue-400">
API Key Detected - CLI Not Installed
</p>
<p className="text-xs text-blue-400/80 mt-1">
{status.recommendation ||
"OPENAI_API_KEY found but Codex CLI not installed. Install the CLI for full agentic capabilities."}
</p>
</div>
</div>
{status.installCommands && (
<div className="space-y-2">
<p className="text-xs font-medium text-foreground-secondary">
Installation Commands:
</p>
<div className="space-y-1">
{status.installCommands.npm && (
<div className="p-2 rounded bg-background border border-border-glass">
<p className="text-xs text-muted-foreground mb-1">npm:</p>
<code className="text-xs text-foreground-secondary font-mono break-all">
{status.installCommands.npm}
</code>
</div>
)}
</div>
</div>
)}
</div>
) : (
<div className="space-y-3">
<div className="flex items-start gap-3 p-3 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<AlertCircle className="w-5 h-5 text-yellow-500 mt-0.5 shrink-0" />
<div className="flex-1">
<p className="text-sm font-medium text-yellow-400">
Codex CLI Not Detected
</p>
<p className="text-xs text-yellow-400/80 mt-1">
{status.recommendation ||
"Install OpenAI Codex CLI to use GPT-5.1 Codex models for autonomous coding."}
</p>
</div>
</div>
{status.installCommands && (
<div className="space-y-2">
<p className="text-xs font-medium text-foreground-secondary">
Installation Commands:
</p>
<div className="space-y-1">
{status.installCommands.npm && (
<div className="p-2 rounded bg-background border border-border-glass">
<p className="text-xs text-muted-foreground mb-1">npm:</p>
<code className="text-xs text-foreground-secondary font-mono break-all">
{status.installCommands.npm}
</code>
</div>
)}
{status.installCommands.macos && (
<div className="p-2 rounded bg-background border border-border-glass">
<p className="text-xs text-muted-foreground mb-1">
macOS (Homebrew):
</p>
<code className="text-xs text-foreground-secondary font-mono break-all">
{status.installCommands.macos}
</code>
</div>
)}
</div>
</div>
)}
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,57 @@
import { Button } from "@/components/ui/button";
import { Trash2, Folder } from "lucide-react";
import type { Project } from "./types";
interface DangerZoneSectionProps {
project: Project | null;
onDeleteClick: () => void;
}
export function DangerZoneSection({
project,
onDeleteClick,
}: DangerZoneSectionProps) {
if (!project) return null;
return (
<div
id="danger"
className="rounded-xl border border-destructive/30 bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-destructive/30">
<div className="flex items-center gap-2 mb-2">
<Trash2 className="w-5 h-5 text-destructive" />
<h2 className="text-lg font-semibold text-foreground">Danger Zone</h2>
</div>
<p className="text-sm text-muted-foreground">
Permanently remove this project from Automaker.
</p>
</div>
<div className="p-6">
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3 min-w-0">
<div className="w-10 h-10 rounded-lg bg-sidebar-accent/20 border border-sidebar-border flex items-center justify-center shrink-0">
<Folder className="w-5 h-5 text-brand-500" />
</div>
<div className="min-w-0">
<p className="font-medium text-foreground truncate">
{project.name}
</p>
<p className="text-xs text-muted-foreground truncate">
{project.path}
</p>
</div>
</div>
<Button
variant="destructive"
onClick={onDeleteClick}
data-testid="delete-project-button"
>
<Trash2 className="w-4 h-4 mr-2" />
Delete Project
</Button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,134 @@
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { FlaskConical, Settings2, TestTube, GitBranch } from "lucide-react";
interface FeatureDefaultsSectionProps {
showProfilesOnly: boolean;
defaultSkipTests: boolean;
useWorktrees: boolean;
onShowProfilesOnlyChange: (value: boolean) => void;
onDefaultSkipTestsChange: (value: boolean) => void;
onUseWorktreesChange: (value: boolean) => void;
}
export function FeatureDefaultsSection({
showProfilesOnly,
defaultSkipTests,
useWorktrees,
onShowProfilesOnlyChange,
onDefaultSkipTestsChange,
onUseWorktreesChange,
}: FeatureDefaultsSectionProps) {
return (
<div
id="defaults"
className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-border">
<div className="flex items-center gap-2 mb-2">
<FlaskConical className="w-5 h-5 text-brand-500" />
<h2 className="text-lg font-semibold text-foreground">
Feature Defaults
</h2>
</div>
<p className="text-sm text-muted-foreground">
Configure default settings for new features.
</p>
</div>
<div className="p-6 space-y-4">
{/* Profiles Only Setting */}
<div className="space-y-3">
<div className="flex items-start space-x-3">
<Checkbox
id="show-profiles-only"
checked={showProfilesOnly}
onCheckedChange={(checked) =>
onShowProfilesOnlyChange(checked === true)
}
className="mt-0.5"
data-testid="show-profiles-only-checkbox"
/>
<div className="space-y-1">
<Label
htmlFor="show-profiles-only"
className="text-foreground cursor-pointer font-medium flex items-center gap-2"
>
<Settings2 className="w-4 h-4 text-brand-500" />
Show profiles only by default
</Label>
<p className="text-xs text-muted-foreground">
When enabled, the Add Feature dialog will show only AI profiles
and hide advanced model tweaking options (Claude SDK, thinking
levels, and OpenAI Codex CLI). This creates a cleaner, less
overwhelming UI. You can always disable this to access advanced
settings.
</p>
</div>
</div>
</div>
{/* Separator */}
<div className="border-t border-border" />
{/* Skip Tests Setting */}
<div className="space-y-3">
<div className="flex items-start space-x-3">
<Checkbox
id="default-skip-tests"
checked={defaultSkipTests}
onCheckedChange={(checked) =>
onDefaultSkipTestsChange(checked === true)
}
className="mt-0.5"
data-testid="default-skip-tests-checkbox"
/>
<div className="space-y-1">
<Label
htmlFor="default-skip-tests"
className="text-foreground cursor-pointer font-medium flex items-center gap-2"
>
<TestTube className="w-4 h-4 text-brand-500" />
Skip automated testing by default
</Label>
<p className="text-xs text-muted-foreground">
When enabled, new features will default to manual verification
instead of TDD (test-driven development). You can still override
this for individual features.
</p>
</div>
</div>
</div>
{/* Worktree Isolation Setting */}
<div className="space-y-3 pt-2 border-t border-border">
<div className="flex items-start space-x-3">
<Checkbox
id="use-worktrees"
checked={useWorktrees}
onCheckedChange={(checked) =>
onUseWorktreesChange(checked === true)
}
className="mt-0.5"
data-testid="use-worktrees-checkbox"
/>
<div className="space-y-1">
<Label
htmlFor="use-worktrees"
className="text-foreground cursor-pointer font-medium flex items-center gap-2"
>
<GitBranch className="w-4 h-4 text-brand-500" />
Enable Git Worktree Isolation (experimental)
</Label>
<p className="text-xs text-muted-foreground">
Creates isolated git branches for each feature. When disabled,
agents work directly in the main project directory. This feature
is experimental and may require additional setup like branch
selection and merge configuration.
</p>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,96 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { LayoutGrid, Minimize2, Square, Maximize2 } from "lucide-react";
import type { KanbanDetailLevel } from "./types";
interface KanbanDisplaySectionProps {
detailLevel: KanbanDetailLevel;
onChange: (level: KanbanDetailLevel) => void;
}
export function KanbanDisplaySection({
detailLevel,
onChange,
}: KanbanDisplaySectionProps) {
return (
<div
id="kanban"
className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-border">
<div className="flex items-center gap-2 mb-2">
<LayoutGrid className="w-5 h-5 text-brand-500" />
<h2 className="text-lg font-semibold text-foreground">
Kanban Card Display
</h2>
</div>
<p className="text-sm text-muted-foreground">
Control how much information is displayed on Kanban cards.
</p>
</div>
<div className="p-6 space-y-4">
<div className="space-y-3">
<Label className="text-foreground">Detail Level</Label>
<div className="grid grid-cols-3 gap-3">
<Button
variant={detailLevel === "minimal" ? "secondary" : "outline"}
onClick={() => onChange("minimal")}
className={`flex flex-col items-center justify-center gap-2 px-4 py-4 h-auto ${
detailLevel === "minimal"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
data-testid="kanban-detail-minimal"
>
<Minimize2 className="w-5 h-5" />
<span className="font-medium text-sm">Minimal</span>
<span className="text-xs text-muted-foreground text-center">
Title & category only
</span>
</Button>
<Button
variant={detailLevel === "standard" ? "secondary" : "outline"}
onClick={() => onChange("standard")}
className={`flex flex-col items-center justify-center gap-2 px-4 py-4 h-auto ${
detailLevel === "standard"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
data-testid="kanban-detail-standard"
>
<Square className="w-5 h-5" />
<span className="font-medium text-sm">Standard</span>
<span className="text-xs text-muted-foreground text-center">
Steps & progress
</span>
</Button>
<Button
variant={detailLevel === "detailed" ? "secondary" : "outline"}
onClick={() => onChange("detailed")}
className={`flex flex-col items-center justify-center gap-2 px-4 py-4 h-auto ${
detailLevel === "detailed"
? "border-brand-500 ring-1 ring-brand-500/50"
: ""
}`}
data-testid="kanban-detail-detailed"
>
<Maximize2 className="w-5 h-5" />
<span className="font-medium text-sm">Detailed</span>
<span className="text-xs text-muted-foreground text-center">
Model, tools & tasks
</span>
</Button>
</div>
<p className="text-xs text-muted-foreground">
<strong>Minimal:</strong> Shows only title and category
<br />
<strong>Standard:</strong> Adds steps preview and progress bar
<br />
<strong>Detailed:</strong> Shows all info including model, tool
calls, task list, and summaries
</p>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,60 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Settings2, Keyboard } from "lucide-react";
interface KeyboardShortcutsSectionProps {
onOpenKeyboardMap: () => void;
}
export function KeyboardShortcutsSection({
onOpenKeyboardMap,
}: KeyboardShortcutsSectionProps) {
return (
<div
id="keyboard"
className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden scroll-mt-6"
>
<div className="p-6 border-b border-border">
<div className="flex items-center gap-2 mb-2">
<Settings2 className="w-5 h-5 text-brand-500" />
<h2 className="text-lg font-semibold text-foreground">
Keyboard Shortcuts
</h2>
</div>
<p className="text-sm text-muted-foreground">
Customize keyboard shortcuts for navigation and actions using the
visual keyboard map.
</p>
</div>
<div className="p-6">
{/* Centered message directing to keyboard map */}
<div className="flex flex-col items-center justify-center py-16 text-center space-y-4">
<div className="relative">
<Keyboard className="w-16 h-16 text-brand-500/30" />
<div className="absolute inset-0 bg-brand-500/10 blur-xl rounded-full" />
</div>
<div className="space-y-2 max-w-md">
<h3 className="text-lg font-semibold text-foreground">
Use the Visual Keyboard Map
</h3>
<p className="text-sm text-muted-foreground">
Click the &quot;View Keyboard Map&quot; button above to customize
your keyboard shortcuts. The visual interface shows all available
keys and lets you easily edit shortcuts with single-modifier
restrictions.
</p>
</div>
<Button
variant="default"
size="lg"
onClick={onOpenKeyboardMap}
className="gap-2 mt-4"
>
<Keyboard className="w-5 h-5" />
Open Keyboard Map
</Button>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,88 @@
import {
type LucideIcon,
Atom,
Cat,
Eclipse,
Flame,
Ghost,
Moon,
Radio,
Snowflake,
Sparkles,
Sun,
Terminal,
Trees,
} from "lucide-react";
import { Theme } from "../types";
export interface ThemeOption {
value: Theme;
label: string;
Icon: LucideIcon;
testId: string;
}
export const themeOptions: ReadonlyArray<ThemeOption> = [
{ value: "dark", label: "Dark", Icon: Moon, testId: "dark-mode-button" },
{ value: "light", label: "Light", Icon: Sun, testId: "light-mode-button" },
{
value: "retro",
label: "Retro",
Icon: Terminal,
testId: "retro-mode-button",
},
{
value: "dracula",
label: "Dracula",
Icon: Ghost,
testId: "dracula-mode-button",
},
{
value: "nord",
label: "Nord",
Icon: Snowflake,
testId: "nord-mode-button",
},
{
value: "monokai",
label: "Monokai",
Icon: Flame,
testId: "monokai-mode-button",
},
{
value: "tokyonight",
label: "Tokyo Night",
Icon: Sparkles,
testId: "tokyonight-mode-button",
},
{
value: "solarized",
label: "Solarized",
Icon: Eclipse,
testId: "solarized-mode-button",
},
{
value: "gruvbox",
label: "Gruvbox",
Icon: Trees,
testId: "gruvbox-mode-button",
},
{
value: "catppuccin",
label: "Catppuccin",
Icon: Cat,
testId: "catppuccin-mode-button",
},
{
value: "onedark",
label: "One Dark",
Icon: Atom,
testId: "onedark-mode-button",
},
{
value: "synthwave",
label: "Synthwave",
Icon: Radio,
testId: "synthwave-mode-button",
},
];

View File

@@ -0,0 +1,47 @@
// Shared TypeScript types for settings view components
export interface CliStatus {
success: boolean;
status?: string;
method?: string;
version?: string;
path?: string;
hasApiKey?: boolean;
recommendation?: string;
installCommands?: {
macos?: string;
windows?: string;
linux?: string;
npm?: string;
};
error?: string;
}
export type Theme =
| "dark"
| "light"
| "retro"
| "dracula"
| "nord"
| "monokai"
| "tokyonight"
| "solarized"
| "gruvbox"
| "catppuccin"
| "onedark"
| "synthwave";
export type KanbanDetailLevel = "minimal" | "standard" | "detailed";
export interface Project {
id: string;
name: string;
path: string;
theme?: Theme;
}
export interface ApiKeys {
anthropic: string;
google: string;
openai: string;
}