2026-02-28 20:42:40 +00:00
import { useState , useEffect } from "react" ;
2026-02-27 04:29:51 +00:00
import DockerSettings from "./DockerSettings" ;
2026-02-27 15:22:49 +00:00
import AwsSettings from "./AwsSettings" ;
2026-02-27 18:39:20 -08:00
import { useSettings } from "../../hooks/useSettings" ;
2026-02-28 21:18:33 +00:00
import { useUpdates } from "../../hooks/useUpdates" ;
2026-03-01 01:21:33 +00:00
import ClaudeInstructionsModal from "../projects/ClaudeInstructionsModal" ;
2026-04-16 08:46:03 -07:00
import ClaudeCodeSettingsModal from "../projects/ClaudeCodeSettingsModal" ;
2026-03-01 01:21:33 +00:00
import EnvVarsModal from "../projects/EnvVarsModal" ;
2026-03-01 15:57:22 +00:00
import { detectHostTimezone } from "../../lib/tauri-commands" ;
2026-03-01 01:21:33 +00:00
import type { EnvVar } from "../../lib/types" ;
2026-03-12 09:35:04 -07:00
import Tooltip from "../ui/Tooltip" ;
2026-03-17 19:31:16 -07:00
import WebTerminalSettings from "./WebTerminalSettings" ;
2026-04-12 20:02:39 -07:00
import SttSettings from "./SttSettings" ;
2026-02-27 04:29:51 +00:00
export default function SettingsPanel() {
2026-02-27 18:39:20 -08:00
const { appSettings , saveSettings } = useSettings ();
2026-03-12 09:26:58 -07:00
const { appVersion , imageUpdateInfo , checkForUpdates , checkImageUpdate } = useUpdates ();
2026-02-28 20:42:40 +00:00
const [ globalInstructions , setGlobalInstructions ] = useState ( appSettings ? . global_claude_instructions ?? "" );
2026-03-01 01:21:33 +00:00
const [ globalEnvVars , setGlobalEnvVars ] = useState < EnvVar [] >( appSettings ? . global_custom_env_vars ?? []);
2026-02-28 21:18:33 +00:00
const [ checkingUpdates , setCheckingUpdates ] = useState ( false );
2026-03-01 15:57:22 +00:00
const [ timezone , setTimezone ] = useState ( appSettings ? . timezone ?? "" );
2026-04-16 08:46:03 -07:00
const [ sshKeyPath , setSshKeyPath ] = useState ( appSettings ? . default_ssh_key_path ?? "" );
const [ gitName , setGitName ] = useState ( appSettings ? . default_git_user_name ?? "" );
const [ gitEmail , setGitEmail ] = useState ( appSettings ? . default_git_user_email ?? "" );
2026-03-01 01:21:33 +00:00
const [ showInstructionsModal , setShowInstructionsModal ] = useState ( false );
const [ showEnvVarsModal , setShowEnvVarsModal ] = useState ( false );
2026-04-16 08:46:03 -07:00
const [ showClaudeCodeSettingsModal , setShowClaudeCodeSettingsModal ] = useState ( false );
2026-02-28 20:42:40 +00:00
// Sync local state when appSettings change
useEffect (() => {
setGlobalInstructions ( appSettings ? . global_claude_instructions ?? "" );
2026-03-01 01:21:33 +00:00
setGlobalEnvVars ( appSettings ? . global_custom_env_vars ?? []);
2026-03-01 15:57:22 +00:00
setTimezone ( appSettings ? . timezone ?? "" );
2026-04-16 08:46:03 -07:00
setSshKeyPath ( appSettings ? . default_ssh_key_path ?? "" );
setGitName ( appSettings ? . default_git_user_name ?? "" );
setGitEmail ( appSettings ? . default_git_user_email ?? "" );
}, [ appSettings ? . global_claude_instructions , appSettings ? . global_custom_env_vars , appSettings ? . timezone , appSettings ? . default_ssh_key_path , appSettings ? . default_git_user_name , appSettings ? . default_git_user_email ]);
2026-03-01 15:57:22 +00:00
// Auto-detect timezone on first load if not yet set
useEffect (() => {
if ( appSettings && ! appSettings . timezone ) {
detectHostTimezone (). then (( tz ) => {
setTimezone ( tz );
saveSettings ({ ... appSettings , timezone : tz });
}). catch (() => {});
}
}, [ appSettings ? . timezone ]);
2026-02-27 18:39:20 -08:00
2026-02-28 21:18:33 +00:00
const handleCheckNow = async () => {
setCheckingUpdates ( true );
try {
2026-03-12 09:26:58 -07:00
await Promise . all ([ checkForUpdates (), checkImageUpdate ()]);
2026-02-28 21:18:33 +00:00
} finally {
setCheckingUpdates ( false );
}
};
const handleAutoCheckToggle = async () => {
if ( ! appSettings ) return ;
await saveSettings ({ ... appSettings , auto_check_updates : ! appSettings . auto_check_updates });
};
2026-02-27 04:29:51 +00:00
return (
< div className = "p-4 space-y-6" >
< h2 className = "text-xs font-semibold uppercase text-[var(--text-secondary)]" >
Settings
</ h2 >
< DockerSettings />
2026-02-27 15:22:49 +00:00
< AwsSettings />
2026-03-01 01:21:33 +00:00
2026-04-16 08:46:03 -07:00
{ /* Default SSH Key Directory */ }
< div >
< label className = "block text-sm font-medium mb-1" > Default SSH Key Directory < Tooltip text = "Global default SSH key directory. Mounted into containers that don't have a per-project SSH path set." /></ label >
< p className = "text-xs text-[var(--text-secondary)] mb-1.5" >
Mounted into all containers unless overridden by a per - project setting .
</ p >
< input
type = "text"
value = { sshKeyPath }
onChange = {( e ) => setSshKeyPath ( e . target . value )}
onBlur = { async () => {
if ( appSettings ) {
await saveSettings ({ ... appSettings , default_ssh_key_path : sshKeyPath || null });
}
}}
placeholder = "~/.ssh"
className = "w-full px-2 py-1 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)]"
/>
</ div >
{ /* Default Git Name */ }
< div >
< label className = "block text-sm font-medium mb-1" > Default Git Name < Tooltip text = "Sets git user.name inside containers. Per-project Git Name takes precedence." /></ label >
< input
type = "text"
value = { gitName }
onChange = {( e ) => setGitName ( e . target . value )}
onBlur = { async () => {
if ( appSettings ) {
await saveSettings ({ ... appSettings , default_git_user_name : gitName || null });
}
}}
placeholder = "Your Name"
className = "w-full px-2 py-1 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)]"
/>
</ div >
{ /* Default Git Email */ }
< div >
< label className = "block text-sm font-medium mb-1" > Default Git Email < Tooltip text = "Sets git user.email inside containers. Per-project Git Email takes precedence." /></ label >
< input
type = "text"
value = { gitEmail }
onChange = {( e ) => setGitEmail ( e . target . value )}
onBlur = { async () => {
if ( appSettings ) {
await saveSettings ({ ... appSettings , default_git_user_email : gitEmail || null });
}
}}
placeholder = "you@example.com"
className = "w-full px-2 py-1 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)]"
/>
</ div >
2026-03-01 15:57:22 +00:00
{ /* Container Timezone */ }
< div >
2026-03-12 09:35:04 -07:00
< label className = "block text-sm font-medium mb-1" > Container Timezone < Tooltip text = "Sets the timezone inside containers. Affects scheduled task timing and log timestamps." /></ label >
2026-03-01 15:57:22 +00:00
< p className = "text-xs text-[var(--text-secondary)] mb-1.5" >
Timezone for containers — affects scheduled task timing ( IANA format , e . g . America / New_York )
</ p >
< input
type = "text"
value = { timezone }
onChange = {( e ) => setTimezone ( e . target . value )}
onBlur = { async () => {
if ( appSettings ) {
await saveSettings ({ ... appSettings , timezone : timezone || null });
}
}}
placeholder = "UTC"
className = "w-full px-2 py-1 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)]"
/>
</ div >
2026-03-01 01:21:33 +00:00
{ /* Global Claude Instructions */ }
2026-02-27 18:39:20 -08:00
< div >
2026-03-12 09:35:04 -07:00
< label className = "block text-sm font-medium mb-1" > Claude Instructions < Tooltip text = "Global instructions applied to all projects. Written to ~/.claude/CLAUDE.md in every container." /></ label >
2026-02-27 18:39:20 -08:00
< p className = "text-xs text-[var(--text-secondary)] mb-1.5" >
Global instructions applied to all projects ( written to ~ /.claude/CLAUDE.md in containers)
</ p >
2026-03-01 01:21:33 +00:00
< div className = "flex items-center justify-between" >
< span className = "text-xs text-[var(--text-secondary)]" >
{ globalInstructions ? "Configured" : "Not set" }
</ span >
< button
onClick = {() => setShowInstructionsModal ( true )}
className = "text-xs px-2 py-0.5 text-[var(--accent)] hover:text-[var(--accent-hover)] hover:bg-[var(--bg-primary)] rounded transition-colors"
>
Edit
</ button >
</ div >
</ div >
{ /* Global Environment Variables */ }
< div >
2026-03-12 09:35:04 -07:00
< label className = "block text-sm font-medium mb-1" > Global Environment Variables < Tooltip text = "Env vars injected into all containers. Per-project vars with the same key take precedence." /></ label >
2026-03-01 01:21:33 +00:00
< p className = "text-xs text-[var(--text-secondary)] mb-1.5" >
Applied to all project containers . Per - project variables override global ones with the same key .
</ p >
< div className = "flex items-center justify-between" >
< span className = "text-xs text-[var(--text-secondary)]" >
{ globalEnvVars . length > 0 ? ` ${ globalEnvVars . length } variable ${ globalEnvVars . length === 1 ? "" : "s" } ` : "None" }
</ span >
< button
onClick = {() => setShowEnvVarsModal ( true )}
className = "text-xs px-2 py-0.5 text-[var(--accent)] hover:text-[var(--accent-hover)] hover:bg-[var(--bg-primary)] rounded transition-colors"
>
Edit
</ button >
</ div >
2026-02-27 18:39:20 -08:00
</ div >
2026-02-28 21:18:33 +00:00
2026-04-16 08:46:03 -07:00
{ /* Global Claude Code Settings */ }
< div >
< label className = "block text-sm font-medium mb-1" > Claude Code Settings < Tooltip text = "Global defaults for Claude Code CLI behavior (TUI mode, effort, focus mode, caching, etc.). Per-project settings override these." /></ label >
< p className = "text-xs text-[var(--text-secondary)] mb-1.5" >
Default Claude Code CLI settings applied to all projects . Per - project settings take precedence .
</ p >
< div className = "flex items-center justify-between" >
< span className = "text-xs text-[var(--text-secondary)]" >
{ appSettings ? . global_claude_code_settings ? "Configured" : "Using defaults" }
</ span >
< button
onClick = {() => setShowClaudeCodeSettingsModal ( true )}
className = "text-xs px-2 py-0.5 text-[var(--accent)] hover:text-[var(--accent-hover)] hover:bg-[var(--bg-primary)] rounded transition-colors"
>
Edit
</ button >
</ div >
</ div >
2026-03-17 19:31:16 -07:00
{ /* Web Terminal */ }
< WebTerminalSettings />
2026-04-12 20:02:39 -07:00
{ /* Speech to Text */ }
< SttSettings />
2026-02-28 21:18:33 +00:00
{ /* Updates section */ }
< div >
2026-03-12 09:35:04 -07:00
< label className = "block text-sm font-medium mb-2" > Updates < Tooltip text = "Check for new versions of the Triple-C app and container image." /></ label >
2026-02-28 21:18:33 +00:00
< div className = "space-y-2" >
{ appVersion && (
< p className = "text-xs text-[var(--text-secondary)]" >
Current version : < span className = "text-[var(--text-primary)] font-mono" >{ appVersion }</ span >
</ p >
)}
< div className = "flex items-center gap-2" >
< label className = "text-xs text-[var(--text-secondary)]" > Auto - check for updates </ label >
< button
onClick = { handleAutoCheckToggle }
className = { `px-2 py-0.5 text-xs rounded transition-colors ${
appSettings ? . auto_check_updates !== false
? "bg-[var(--success)] text-white"
: "bg-[var(--bg-primary)] border border-[var(--border-color)] text-[var(--text-secondary)]"
} ` }
>
{ appSettings ? . auto_check_updates !== false ? "ON" : "OFF" }
</ button >
</ div >
< button
onClick = { handleCheckNow }
disabled = { checkingUpdates }
className = "px-3 py-1.5 text-xs bg-[var(--bg-primary)] border border-[var(--border-color)] rounded hover:bg-[var(--border-color)] disabled:opacity-50 transition-colors"
>
{ checkingUpdates ? "Checking..." : "Check now" }
</ button >
2026-03-12 09:26:58 -07:00
{ imageUpdateInfo && (
< div className = "flex items-center gap-2 px-3 py-2 text-xs bg-[var(--bg-primary)] border border-[var(--warning,#f59e0b)] rounded" >
< span className = "inline-block w-2 h-2 rounded-full bg-[var(--warning,#f59e0b)]" />
< span > A newer container image is available . Re - pull the image in Docker settings above to update .</ span >
</ div >
)}
2026-02-28 21:18:33 +00:00
</ div >
</ div >
2026-03-01 01:21:33 +00:00
{ showInstructionsModal && (
< ClaudeInstructionsModal
instructions = { globalInstructions }
disabled = { false }
onSave = { async ( instructions ) => {
setGlobalInstructions ( instructions );
if ( appSettings ) {
await saveSettings ({ ... appSettings , global_claude_instructions : instructions || null });
}
}}
onClose = {() => setShowInstructionsModal ( false )}
/>
)}
{ showEnvVarsModal && (
< EnvVarsModal
envVars = { globalEnvVars }
disabled = { false }
onSave = { async ( vars ) => {
setGlobalEnvVars ( vars );
if ( appSettings ) {
await saveSettings ({ ... appSettings , global_custom_env_vars : vars });
}
}}
onClose = {() => setShowEnvVarsModal ( false )}
/>
)}
2026-04-16 08:46:03 -07:00
{ showClaudeCodeSettingsModal && (
< ClaudeCodeSettingsModal
settings = { appSettings ? . global_claude_code_settings ?? null }
disabled = { false }
onSave = { async ( ccSettings ) => {
if ( appSettings ) {
await saveSettings ({ ... appSettings , global_claude_code_settings : ccSettings });
}
}}
onClose = {() => setShowClaudeCodeSettingsModal ( false )}
/>
)}
2026-02-27 04:29:51 +00:00
</ div >
);
}