♿ startup wizard
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user