e49d049455
- Move GeneralSettingsTab to components/features/settings/ - Re-export from page for backward compatibility - Slim SettingsPage from 284 to ~150 lines Quality gates: tsc --noEmit passes, npm run build passes
138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Link, Outlet, useLocation } from "react-router-dom";
|
|
import {
|
|
getUserConfig,
|
|
updateUserConfig,
|
|
type UserConfig,
|
|
type UserConfigUpdate,
|
|
} from "../api/settings";
|
|
import { ErrorState, LoadingState } from "../components/data-states";
|
|
import { useAsyncData } from "../hooks/use-async-data";
|
|
|
|
const TABS = [
|
|
{ label: "General", path: "general" },
|
|
{ label: "SSH Keys", path: "ssh-keys" },
|
|
] as const;
|
|
|
|
export { GeneralSettingsTab } from "../components/features/settings/GeneralSettingsTab";
|
|
|
|
export const SettingsPage = () => {
|
|
const location = useLocation();
|
|
const {
|
|
data: loadedConfig,
|
|
status,
|
|
reload,
|
|
} = useAsyncData<UserConfig>(getUserConfig, []);
|
|
const [config, setConfig] = useState<UserConfig>({
|
|
theme: "system",
|
|
default_editor: null,
|
|
git_user_name: null,
|
|
git_user_email: null,
|
|
last_session_id: null,
|
|
notification_toast_level: "all",
|
|
notification_mute_categories: [],
|
|
});
|
|
const [saveStatus, setSaveStatus] = useState<
|
|
"idle" | "saving" | "saved" | "error"
|
|
>("idle");
|
|
|
|
useEffect(() => {
|
|
if (loadedConfig) {
|
|
setConfig({
|
|
...loadedConfig,
|
|
notification_toast_level:
|
|
loadedConfig.notification_toast_level ?? "all",
|
|
notification_mute_categories:
|
|
loadedConfig.notification_mute_categories ?? [],
|
|
});
|
|
}
|
|
}, [loadedConfig]);
|
|
|
|
const handleChange = (
|
|
key: keyof UserConfigUpdate,
|
|
value: string | string[] | null,
|
|
) => {
|
|
setConfig((prev) => ({ ...prev, [key]: value }) as UserConfig);
|
|
setSaveStatus("idle");
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
setSaveStatus("saving");
|
|
try {
|
|
const update: UserConfigUpdate = {
|
|
theme: config.theme,
|
|
default_editor: config.default_editor,
|
|
git_user_name: config.git_user_name,
|
|
git_user_email: config.git_user_email,
|
|
notification_toast_level: config.notification_toast_level,
|
|
notification_mute_categories: config.notification_mute_categories,
|
|
};
|
|
const updated = await updateUserConfig(update);
|
|
setConfig(updated);
|
|
window.dispatchEvent(
|
|
new CustomEvent("userconfig:updated", { detail: updated }),
|
|
);
|
|
setSaveStatus("saved");
|
|
if (updated.theme === "system") {
|
|
document.documentElement.removeAttribute("data-theme");
|
|
} else {
|
|
document.documentElement.setAttribute("data-theme", updated.theme);
|
|
}
|
|
window.setTimeout(() => setSaveStatus("idle"), 2000);
|
|
} catch {
|
|
setSaveStatus("error");
|
|
}
|
|
};
|
|
|
|
if (status === "loading") {
|
|
return (
|
|
<section className="stack">
|
|
<LoadingState message="Loading settings..." />
|
|
</section>
|
|
);
|
|
}
|
|
|
|
if (status === "error") {
|
|
return (
|
|
<section className="stack">
|
|
<ErrorState message="Failed to load settings" onRetry={reload} />
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const parts = location.pathname.split("/").filter(Boolean);
|
|
const activePath = location.pathname.endsWith("/settings")
|
|
? "general"
|
|
: (parts[parts.length - 1] ?? "general");
|
|
|
|
return (
|
|
<section className="stack settings-page">
|
|
<header className="settings-header card stack-sm">
|
|
<div>
|
|
<p className="eyebrow">Configuration</p>
|
|
<h1>Settings</h1>
|
|
</div>
|
|
<p className="muted">
|
|
General preferences, SSH keys, and config profiles.
|
|
</p>
|
|
</header>
|
|
|
|
<nav className="settings-tabs" aria-label="Settings sections">
|
|
{TABS.map((tab) => (
|
|
<Link
|
|
key={tab.path}
|
|
className={`settings-tab ${activePath === tab.path ? "active" : ""}`}
|
|
to={tab.path === "general" ? "/settings" : `/settings/${tab.path}`}
|
|
>
|
|
{tab.label}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
<div className="settings-panel card">
|
|
<Outlet context={{ config, handleChange, handleSave, saveStatus }} />
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|