diff --git a/app/src/components/views/settings-view.tsx b/app/src/components/views/settings-view.tsx index 8f6806ec..41488a24 100644 --- a/app/src/components/views/settings-view.tsx +++ b/app/src/components/views/settings-view.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useAppStore } from "@/store/app-store"; import { Button } from "@/components/ui/button"; import { useCliStatus } from "./settings-view/hooks/use-cli-status"; -import { useScrollTracking } from "./settings-view/hooks/use-scroll-tracking"; +import { useScrollTracking } from "@/hooks/use-scroll-tracking"; import { NAV_ITEMS } from "./settings-view/config/navigation"; import { SettingsHeader } from "./settings-view/components/settings-header"; import { KeyboardMapDialog } from "./settings-view/components/keyboard-map-dialog"; @@ -76,7 +76,11 @@ export function SettingsView() { // Use scroll tracking hook const { activeSection, scrollToSection, scrollContainerRef } = - useScrollTracking(NAV_ITEMS, currentProject); + useScrollTracking({ + items: NAV_ITEMS, + filterFn: (item) => item.id !== "danger" || !!currentProject, + initialSection: "api-keys", + }); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showKeyboardMapDialog, setShowKeyboardMapDialog] = useState(false); diff --git a/app/src/components/views/settings-view/api-keys/api-key-field.tsx b/app/src/components/views/settings-view/api-keys/api-key-field.tsx index b62f4d9a..db281813 100644 --- a/app/src/components/views/settings-view/api-keys/api-key-field.tsx +++ b/app/src/components/views/settings-view/api-keys/api-key-field.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { AlertCircle, CheckCircle2, Eye, EyeOff, Loader2, Zap } from "lucide-react"; -import type { ProviderConfig } from "./shared/api-provider-config"; +import type { ProviderConfig } from "@/config/api-providers"; interface ApiKeyFieldProps { config: ProviderConfig; diff --git a/app/src/components/views/settings-view/api-keys/api-keys-section.tsx b/app/src/components/views/settings-view/api-keys/api-keys-section.tsx index 8e6711e5..33c89f8a 100644 --- a/app/src/components/views/settings-view/api-keys/api-keys-section.tsx +++ b/app/src/components/views/settings-view/api-keys/api-keys-section.tsx @@ -3,7 +3,7 @@ import { useSetupStore } from "@/store/setup-store"; import { Button } from "@/components/ui/button"; import { Key, CheckCircle2 } from "lucide-react"; import { ApiKeyField } from "./api-key-field"; -import { buildProviderConfigs } from "./shared/api-provider-config"; +import { buildProviderConfigs } from "@/config/api-providers"; import { AuthenticationStatusDisplay } from "./authentication-status-display"; import { SecurityNotice } from "./security-notice"; import { useApiKeyManagement } from "./hooks/use-api-key-management"; diff --git a/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts b/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts index 6b284540..f45939ed 100644 --- a/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts +++ b/app/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { useAppStore } from "@/store/app-store"; import { getElectronAPI } from "@/lib/electron"; -import type { ProviderConfigParams } from "../config/api-provider-config"; +import type { ProviderConfigParams } from "@/config/api-providers"; interface TestResult { success: boolean; diff --git a/app/src/components/views/settings-view/appearance/appearance-section.tsx b/app/src/components/views/settings-view/appearance/appearance-section.tsx index e24c355d..90a5c8de 100644 --- a/app/src/components/views/settings-view/appearance/appearance-section.tsx +++ b/app/src/components/views/settings-view/appearance/appearance-section.tsx @@ -1,7 +1,7 @@ import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Palette } from "lucide-react"; -import { themeOptions } from "./config/theme-options"; +import { themeOptions } from "@/config/theme-options"; import type { Theme, Project } from "../shared/types"; interface AppearanceSectionProps { 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 deleted file mode 100644 index 79f5b63e..00000000 --- a/app/src/components/views/settings-view/shared/api-provider-config.ts +++ /dev/null @@ -1,148 +0,0 @@ -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", - }, -]; diff --git a/app/src/components/views/settings-view/api-keys/config/api-provider-config.ts b/app/src/config/api-providers.ts similarity index 100% rename from app/src/components/views/settings-view/api-keys/config/api-provider-config.ts rename to app/src/config/api-providers.ts diff --git a/app/src/components/views/settings-view/appearance/config/theme-options.ts b/app/src/config/theme-options.ts similarity index 95% rename from app/src/components/views/settings-view/appearance/config/theme-options.ts rename to app/src/config/theme-options.ts index 9e52deb0..ac8bc567 100644 --- a/app/src/components/views/settings-view/appearance/config/theme-options.ts +++ b/app/src/config/theme-options.ts @@ -13,7 +13,7 @@ import { Terminal, Trees, } from "lucide-react"; -import { Theme } from "../../shared/types"; +import { Theme } from "@/components/views/settings-view/shared/types"; export interface ThemeOption { value: Theme; diff --git a/app/src/components/views/settings-view/hooks/use-scroll-tracking.ts b/app/src/hooks/use-scroll-tracking.ts similarity index 54% rename from app/src/components/views/settings-view/hooks/use-scroll-tracking.ts rename to app/src/hooks/use-scroll-tracking.ts index 1d76b5a7..25f86a13 100644 --- a/app/src/components/views/settings-view/hooks/use-scroll-tracking.ts +++ b/app/src/hooks/use-scroll-tracking.ts @@ -1,17 +1,34 @@ import { useState, useEffect, useRef, useCallback } from "react"; -import type { Project } from "@/store/app-store"; -import type { NavigationItem } from "../config/navigation"; + +interface ScrollTrackingItem { + id: string; +} + +interface UseScrollTrackingOptions { + /** Navigation items with at least an id property */ + items: T[]; + /** Optional filter function to determine which items should be tracked */ + filterFn?: (item: T) => boolean; + /** Optional initial active section (defaults to first item's id) */ + initialSection?: string; + /** Optional offset from top when scrolling to section (defaults to 24) */ + scrollOffset?: number; +} /** - * Custom hook for managing scroll-based navigation tracking + * Generic custom hook for managing scroll-based navigation tracking * Automatically highlights the active section based on scroll position * and provides smooth scrolling to sections */ -export function useScrollTracking( - navItems: NavigationItem[], - currentProject: Project | null -) { - const [activeSection, setActiveSection] = useState("api-keys"); +export function useScrollTracking({ + items, + filterFn = () => true, + initialSection, + scrollOffset = 24, +}: UseScrollTrackingOptions) { + const [activeSection, setActiveSection] = useState( + initialSection || items[0]?.id || "" + ); const scrollContainerRef = useRef(null); // Track scroll position to highlight active nav item @@ -20,8 +37,8 @@ export function useScrollTracking( if (!container) return; const handleScroll = () => { - const sections = navItems - .filter((item) => item.id !== "danger" || currentProject) + const sections = items + .filter(filterFn) .map((item) => ({ id: item.id, element: document.getElementById(item.id), @@ -57,24 +74,27 @@ export function useScrollTracking( container.addEventListener("scroll", handleScroll); return () => container.removeEventListener("scroll", handleScroll); - }, [currentProject, navItems]); + }, [items, filterFn]); // Scroll to a specific section with smooth animation - const scrollToSection = useCallback((sectionId: string) => { - const element = document.getElementById(sectionId); - if (element && scrollContainerRef.current) { - const container = scrollContainerRef.current; - const containerRect = container.getBoundingClientRect(); - const elementRect = element.getBoundingClientRect(); - const relativeTop = - elementRect.top - containerRect.top + container.scrollTop; + const scrollToSection = useCallback( + (sectionId: string) => { + const element = document.getElementById(sectionId); + if (element && scrollContainerRef.current) { + const container = scrollContainerRef.current; + const containerRect = container.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + const relativeTop = + elementRect.top - containerRect.top + container.scrollTop; - container.scrollTo({ - top: relativeTop - 24, - behavior: "smooth", - }); - } - }, []); + container.scrollTo({ + top: relativeTop - scrollOffset, + behavior: "smooth", + }); + } + }, + [scrollOffset] + ); return { activeSection,