startup wizard

This commit is contained in:
2026-03-06 22:18:12 +10:00
parent 19e584dc47
commit eb196ffc45
5 changed files with 80 additions and 32 deletions

View File

@@ -1,3 +1,10 @@
.settings-setup-subtitle {
font-size: 13px;
color: var(--text-secondary);
padding: 0 20px 4px;
margin: 0;
}
.settings-overlay {
position: fixed;
inset: 0;

View File

@@ -3,9 +3,11 @@ import './Settings.css'
interface Props {
onClose: () => void
onProjectChanged?: () => void
isSetup?: boolean
}
export function SettingsDialog({ onClose }: Props): JSX.Element {
export function SettingsDialog({ onClose, onProjectChanged, isSetup }: Props): JSX.Element {
const [apiKey, setApiKey] = useState('')
const [projectPath, setProjectPath] = useState('')
const [originalPath, setOriginalPath] = useState('')
@@ -21,15 +23,16 @@ export function SettingsDialog({ onClose }: Props): JSX.Element {
}, [])
useEffect(() => {
if (isSetup) return
const handler = (e: KeyboardEvent): void => {
if (e.key === 'Escape') onClose()
}
window.addEventListener('keydown', handler)
return () => window.removeEventListener('keydown', handler)
}, [onClose])
}, [onClose, isSetup])
const handleOverlayClick = (e: React.MouseEvent): void => {
if (e.target === overlayRef.current) onClose()
if (!isSetup && e.target === overlayRef.current) onClose()
}
const handleBrowse = async (): Promise<void> => {
@@ -39,36 +42,27 @@ export function SettingsDialog({ onClose }: Props): JSX.Element {
const handleSave = async (): Promise<void> => {
await window.api.writeConfig({ apiKey: apiKey || undefined, projectPath: projectPath || undefined })
if (projectPath !== originalPath) onProjectChanged?.()
setOriginalPath(projectPath)
setSaved(true)
setTimeout(() => setSaved(false), 2000)
setTimeout(() => { setSaved(false); onClose() }, 800)
}
const pathChanged = projectPath !== originalPath
return (
<div className="settings-overlay" ref={overlayRef} onClick={handleOverlayClick}>
<div className="settings-dialog" role="dialog" aria-modal="true" aria-label="Preferences">
<div className="settings-dialog" role="dialog" aria-modal="true" aria-label={isSetup ? 'Welcome' : 'Preferences'}>
<div className="settings-header">
<span className="settings-title">Preferences</span>
<button className="settings-close" onClick={onClose} aria-label="Close"></button>
<span className="settings-title">{isSetup ? 'Welcome to Hohoff' : 'Preferences'}</span>
{!isSetup && (
<button className="settings-close" onClick={onClose} aria-label="Close"></button>
)}
</div>
<div className="settings-body">
<div className="settings-field">
<label className="settings-label" htmlFor="settings-api-key">Anthropic API Key</label>
<input
id="settings-api-key"
className="settings-input"
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="sk-ant-…"
autoComplete="off"
spellCheck={false}
/>
<p className="settings-hint">Changes take effect immediately no restart needed.</p>
</div>
{isSetup && (
<p className="settings-setup-subtitle">Configure your project folder and API key to get started.</p>
)}
<div className="settings-body">
<div className="settings-field">
<label className="settings-label" htmlFor="settings-project-path">Project Folder</label>
<div className="settings-path-row">
@@ -83,16 +77,30 @@ export function SettingsDialog({ onClose }: Props): JSX.Element {
/>
<button className="settings-browse-btn" onClick={handleBrowse}>Browse</button>
</div>
{pathChanged && (
<p className="settings-hint settings-hint--warn">Restart the app to load the new project.</p>
)}
</div>
<div className="settings-field">
<label className="settings-label" htmlFor="settings-api-key">Anthropic API Key</label>
<input
id="settings-api-key"
className="settings-input"
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="sk-ant-…"
autoComplete="off"
spellCheck={false}
/>
<p className="settings-hint">Used for AI features. Changes take effect immediately.</p>
</div>
</div>
<div className="settings-footer">
<span className="settings-config-path">Config: ~/.hohoff/config.json</span>
<div className="settings-footer-actions">
<button className="settings-btn settings-btn--secondary" onClick={onClose}>Cancel</button>
{!isSetup && (
<button className="settings-btn settings-btn--secondary" onClick={onClose}>Cancel</button>
)}
<button className="settings-btn settings-btn--primary" onClick={handleSave}>
{saved ? 'Saved ✓' : 'Save'}
</button>