diff --git a/app/src/components/views/settings-view.tsx b/app/src/components/views/settings-view.tsx
index 2576c88a..8f6806ec 100644
--- a/app/src/components/views/settings-view.tsx
+++ b/app/src/components/views/settings-view.tsx
@@ -18,6 +18,8 @@ import { KanbanDisplaySection } from "./settings-view/kanban-display/kanban-disp
import { KeyboardShortcutsSection } from "./settings-view/keyboard-shortcuts/keyboard-shortcuts-section";
import { FeatureDefaultsSection } from "./settings-view/feature-defaults/feature-defaults-section";
import { DangerZoneSection } from "./settings-view/danger-zone/danger-zone-section";
+import type { Project as SettingsProject, Theme } from "./settings-view/shared/types";
+import type { Project as ElectronProject } from "@/lib/electron";
export function SettingsView() {
const {
@@ -37,8 +39,21 @@ export function SettingsView() {
moveProjectToTrash,
} = useAppStore();
+ // Convert electron Project to settings-view Project type
+ const convertProject = (project: ElectronProject | null): SettingsProject | null => {
+ if (!project) return null;
+ return {
+ id: project.id,
+ name: project.name,
+ path: project.path,
+ theme: project.theme as Theme | undefined,
+ };
+ };
+
+ const settingsProject = convertProject(currentProject);
+
// Compute the effective theme for the current project
- const effectiveTheme = currentProject?.theme || theme;
+ const effectiveTheme = (settingsProject?.theme || theme) as Theme;
// Handler to set theme - saves to project if one is selected, otherwise to global
const handleSetTheme = (newTheme: typeof theme) => {
@@ -111,7 +126,7 @@ export function SettingsView() {
{/* Appearance Section */}
@@ -138,7 +153,7 @@ export function SettingsView() {
{/* Danger Zone Section - Only show when a project is selected */}
setShowDeleteDialog(true)}
/>
diff --git a/app/src/components/views/settings-view/shared/api-provider-config.ts b/app/src/components/views/settings-view/shared/api-provider-config.ts
new file mode 100644
index 00000000..79f5b63e
--- /dev/null
+++ b/app/src/components/views/settings-view/shared/api-provider-config.ts
@@ -0,0 +1,148 @@
+import type { Dispatch, SetStateAction } from "react";
+import type { ApiKeys } from "@/store/app-store";
+
+export type ProviderKey = "anthropic" | "google" | "openai";
+
+export interface ProviderConfig {
+ key: ProviderKey;
+ label: string;
+ inputId: string;
+ placeholder: string;
+ value: string;
+ setValue: Dispatch>;
+ showValue: boolean;
+ setShowValue: Dispatch>;
+ hasStoredKey: string | null | undefined;
+ inputTestId: string;
+ toggleTestId: string;
+ testButton: {
+ onClick: () => Promise | void;
+ disabled: boolean;
+ loading: boolean;
+ testId: string;
+ };
+ result: { success: boolean; message: string } | null;
+ resultTestId: string;
+ resultMessageTestId: string;
+ descriptionPrefix: string;
+ descriptionLinkHref: string;
+ descriptionLinkText: string;
+ descriptionSuffix?: string;
+}
+
+export interface ProviderConfigParams {
+ apiKeys: ApiKeys;
+ anthropic: {
+ value: string;
+ setValue: Dispatch>;
+ show: boolean;
+ setShow: Dispatch>;
+ testing: boolean;
+ onTest: () => Promise;
+ result: { success: boolean; message: string } | null;
+ };
+ google: {
+ value: string;
+ setValue: Dispatch>;
+ show: boolean;
+ setShow: Dispatch>;
+ testing: boolean;
+ onTest: () => Promise;
+ result: { success: boolean; message: string } | null;
+ };
+ openai: {
+ value: string;
+ setValue: Dispatch>;
+ show: boolean;
+ setShow: Dispatch>;
+ testing: boolean;
+ onTest: () => Promise;
+ result: { success: boolean; message: string } | null;
+ };
+}
+
+export const buildProviderConfigs = ({
+ apiKeys,
+ anthropic,
+ google,
+ openai,
+}: ProviderConfigParams): ProviderConfig[] => [
+ {
+ key: "anthropic",
+ label: "Anthropic API Key (Claude)",
+ inputId: "anthropic-key",
+ placeholder: "sk-ant-...",
+ value: anthropic.value,
+ setValue: anthropic.setValue,
+ showValue: anthropic.show,
+ setShowValue: anthropic.setShow,
+ hasStoredKey: apiKeys.anthropic,
+ inputTestId: "anthropic-api-key-input",
+ toggleTestId: "toggle-anthropic-visibility",
+ testButton: {
+ onClick: anthropic.onTest,
+ disabled: !anthropic.value || anthropic.testing,
+ loading: anthropic.testing,
+ testId: "test-claude-connection",
+ },
+ result: anthropic.result,
+ resultTestId: "test-connection-result",
+ resultMessageTestId: "test-connection-message",
+ descriptionPrefix: "Used for Claude AI features. Get your key at",
+ descriptionLinkHref: "https://console.anthropic.com/account/keys",
+ descriptionLinkText: "console.anthropic.com",
+ descriptionSuffix:
+ ". Alternatively, the CLAUDE_CODE_OAUTH_TOKEN environment variable can be used.",
+ },
+ {
+ key: "google",
+ label: "Google API Key (Gemini)",
+ inputId: "google-key",
+ placeholder: "AIza...",
+ value: google.value,
+ setValue: google.setValue,
+ showValue: google.show,
+ setShowValue: google.setShow,
+ hasStoredKey: apiKeys.google,
+ inputTestId: "google-api-key-input",
+ toggleTestId: "toggle-google-visibility",
+ testButton: {
+ onClick: google.onTest,
+ disabled: !google.value || google.testing,
+ loading: google.testing,
+ testId: "test-gemini-connection",
+ },
+ result: google.result,
+ resultTestId: "gemini-test-connection-result",
+ resultMessageTestId: "gemini-test-connection-message",
+ descriptionPrefix:
+ "Used for Gemini AI features (including image/design prompts). Get your key at",
+ descriptionLinkHref: "https://makersuite.google.com/app/apikey",
+ descriptionLinkText: "makersuite.google.com",
+ },
+ {
+ key: "openai",
+ label: "OpenAI API Key (Codex/GPT)",
+ inputId: "openai-key",
+ placeholder: "sk-...",
+ value: openai.value,
+ setValue: openai.setValue,
+ showValue: openai.show,
+ setShowValue: openai.setShow,
+ hasStoredKey: apiKeys.openai,
+ inputTestId: "openai-api-key-input",
+ toggleTestId: "toggle-openai-visibility",
+ testButton: {
+ onClick: openai.onTest,
+ disabled: !openai.value || openai.testing,
+ loading: openai.testing,
+ testId: "test-openai-connection",
+ },
+ result: openai.result,
+ resultTestId: "openai-test-connection-result",
+ resultMessageTestId: "openai-test-connection-message",
+ descriptionPrefix: "Used for OpenAI Codex CLI and GPT models. Get your key at",
+ descriptionLinkHref: "https://platform.openai.com/api-keys",
+ descriptionLinkText: "platform.openai.com",
+ },
+];