From d5b1d6eb9d2f347e8e979a0d13a3eb4d8c1b7b83 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Thu, 12 Feb 2026 17:28:29 +0800 Subject: [PATCH] feat: redesign settings page with sidebar navigation Extract settings into General / Providers / Claude CLI sections with a left sidebar layout. Keep Skills and MCP on the Extensions page. Co-Authored-By: Claude Opus 4.6 --- src/app/settings/page.tsx | 547 +----------------- src/components/layout/NavRail.tsx | 1 - .../settings/CliSettingsSection.tsx | 322 +++++++++++ src/components/settings/GeneralSection.tsx | 189 ++++++ src/components/settings/SettingsLayout.tsx | 98 ++++ 5 files changed, 613 insertions(+), 544 deletions(-) create mode 100644 src/components/settings/CliSettingsSection.tsx create mode 100644 src/components/settings/GeneralSection.tsx create mode 100644 src/components/settings/SettingsLayout.tsx diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index e9b22f3..9b942b4 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -1,52 +1,9 @@ "use client"; -import { Suspense, useState, useEffect, useCallback } from "react"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +import { Suspense } from "react"; import { HugeiconsIcon } from "@hugeicons/react"; -import { - FloppyDiskIcon, - ReloadIcon, - CodeIcon, - SlidersHorizontalIcon, - Loading02Icon, -} from "@hugeicons/core-free-icons"; -import { ProviderManager } from "@/components/settings/ProviderManager"; -import { useUpdate } from "@/hooks/useUpdate"; - -interface SettingsData { - [key: string]: unknown; -} - -// Structured known fields from ~/.claude/settings.json -const KNOWN_FIELDS = [ - { - key: "permissions", - label: "Permissions", - description: "Configure permission settings for Claude CLI", - type: "object" as const, - }, - { - key: "env", - label: "Environment Variables", - description: "Environment variables passed to Claude", - type: "object" as const, - }, -] as const; +import { Loading02Icon } from "@hugeicons/core-free-icons"; +import { SettingsLayout } from "@/components/settings/SettingsLayout"; export default function SettingsPage() { return ( @@ -57,503 +14,7 @@ export default function SettingsPage() { } > - + ); } - -// --- About & Updates card --- -function UpdateCard() { - const { updateInfo, checking, checkForUpdates } = useUpdate(); - const currentVersion = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0"; - - return ( -
-
-
-

CodePilot

-

Version {currentVersion}

-
- -
- - {updateInfo && !checking && ( -
- {updateInfo.updateAvailable ? ( -
- - - Update available: v{updateInfo.latestVersion} - - -
- ) : ( -

You're on the latest version.

- )} -
- )} -
- ); -} - -// --- Claude CLI Settings Section (manages ~/.claude/settings.json) --- -function SettingsPageInner() { - const [settings, setSettings] = useState({}); - const [originalSettings, setOriginalSettings] = useState({}); - const [jsonText, setJsonText] = useState(""); - const [jsonError, setJsonError] = useState(""); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); - const [saveSuccess, setSaveSuccess] = useState(false); - const [showConfirmDialog, setShowConfirmDialog] = useState(false); - const [pendingSaveAction, setPendingSaveAction] = useState< - "form" | "json" | null - >(null); - - // Skip-permissions toggle state - const [skipPermissions, setSkipPermissions] = useState(false); - const [showSkipPermWarning, setShowSkipPermWarning] = useState(false); - const [skipPermSaving, setSkipPermSaving] = useState(false); - - const fetchSettings = useCallback(async () => { - try { - const res = await fetch("/api/settings"); - if (res.ok) { - const data = await res.json(); - const s = data.settings || {}; - setSettings(s); - setOriginalSettings(s); - setJsonText(JSON.stringify(s, null, 2)); - } - } catch { - setSettings({}); - setOriginalSettings({}); - setJsonText("{}"); - } finally { - setLoading(false); - } - }, []); - - // Fetch app-level settings (dangerously_skip_permissions) - const fetchAppSettings = useCallback(async () => { - try { - const res = await fetch("/api/settings/app"); - if (res.ok) { - const data = await res.json(); - const appSettings = data.settings || {}; - setSkipPermissions(appSettings.dangerously_skip_permissions === "true"); - } - } catch { - // ignore - } - }, []); - - useEffect(() => { - fetchSettings(); - fetchAppSettings(); - }, [fetchSettings, fetchAppSettings]); - - const hasChanges = - JSON.stringify(settings) !== JSON.stringify(originalSettings); - - const handleSave = async (source: "form" | "json") => { - let dataToSave: SettingsData; - - if (source === "json") { - try { - dataToSave = JSON.parse(jsonText); - setJsonError(""); - } catch { - setJsonError("Invalid JSON format"); - return; - } - } else { - dataToSave = settings; - } - - setSaving(true); - try { - const res = await fetch("/api/settings", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ settings: dataToSave }), - }); - - if (res.ok) { - setSettings(dataToSave); - setOriginalSettings(dataToSave); - setJsonText(JSON.stringify(dataToSave, null, 2)); - setSaveSuccess(true); - setTimeout(() => setSaveSuccess(false), 2000); - } - } catch { - // Handle error silently - } finally { - setSaving(false); - setShowConfirmDialog(false); - setPendingSaveAction(null); - } - }; - - const handleReset = () => { - setSettings(originalSettings); - setJsonText(JSON.stringify(originalSettings, null, 2)); - setJsonError(""); - }; - - const handleFormatJson = () => { - try { - const parsed = JSON.parse(jsonText); - setJsonText(JSON.stringify(parsed, null, 2)); - setJsonError(""); - } catch { - setJsonError("Cannot format: invalid JSON"); - } - }; - - const confirmSave = (source: "form" | "json") => { - setPendingSaveAction(source); - setShowConfirmDialog(true); - }; - - const updateField = (key: string, value: unknown) => { - setSettings((prev) => ({ ...prev, [key]: value })); - }; - - // Handle skip-permissions toggle - const handleSkipPermToggle = (checked: boolean) => { - if (checked) { - // Show warning dialog before enabling - setShowSkipPermWarning(true); - } else { - // Disable immediately without warning - saveSkipPermissions(false); - } - }; - - const saveSkipPermissions = async (enabled: boolean) => { - setSkipPermSaving(true); - try { - const res = await fetch("/api/settings/app", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - settings: { dangerously_skip_permissions: enabled ? "true" : "" }, - }), - }); - if (res.ok) { - setSkipPermissions(enabled); - } - } catch { - // ignore - } finally { - setSkipPermSaving(false); - setShowSkipPermWarning(false); - } - }; - - return ( -
-
-

Settings

-

- Manage CodePilot and Claude CLI settings -

-
- -
-
- - - - {/* Dangerous Settings */} -
-
-
-

Auto-approve All Actions

-

- Skip all permission checks and auto-approve every tool action. - This is dangerous and should only be used for trusted tasks. -

-
- -
- {skipPermissions && ( -
- - All tool actions will be auto-approved without confirmation. Use with caution. -
- )} -
- - {loading ? ( -
- - - Loading settings... - -
- ) : ( - - - - - Visual Editor - - - - JSON Editor - - - - -
- {KNOWN_FIELDS.map((field) => ( -
- -

- {field.description} -

-