import { useEffect, useState } from "react"; import { Link, Outlet, useLocation, useOutletContext } from "react-router-dom"; import { getUserConfig, updateUserConfig, type UserConfig, type UserConfigUpdate, } from "../api/settings"; import { ErrorState, LoadingState } from "../components/data-states"; import { Icon } from "../components/icon"; import { useAsyncData } from "../hooks/use-async-data"; const TABS = [ { label: "General", path: "general" }, { label: "SSH Keys", path: "ssh-keys" }, ] as const; const THEME_OPTIONS = [ { value: "system", label: "System" }, { value: "light", label: "Light" }, { value: "dark", label: "Dark" }, ]; const TOAST_LEVEL_OPTIONS = [ { value: "all", label: "All" }, { value: "errors", label: "Errors only" }, { value: "none", label: "None" }, ]; const MUTE_CATEGORIES = ["instance", "system", "health", "security"]; type SettingsOutletContext = { config: UserConfig; handleChange: ( key: keyof UserConfigUpdate, value: string | string[] | null, ) => void; handleSave: () => Promise; saveStatus: "idle" | "saving" | "saved" | "error"; }; export const SettingsPage = () => { const location = useLocation(); const { data: loadedConfig, status, reload, } = useAsyncData(getUserConfig, []); const [config, setConfig] = useState({ 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"); // Sync loaded config into local editable state 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 (
); } if (status === "error") { return (
); } const parts = location.pathname.split("/").filter(Boolean); const activePath = location.pathname.endsWith("/settings") ? "general" : (parts[parts.length - 1] ?? "general"); return (

Configuration

Settings

General preferences, SSH keys, and config profiles.

); }; export const GeneralSettingsTab = () => { const { config, handleChange, handleSave, saveStatus } = useOutletContext(); return (

General

Notifications

Mute categories
{MUTE_CATEGORIES.map((cat) => ( ))}
{saveStatus === "saved" && ( Settings saved! )} {saveStatus === "error" && ( Failed to save )}
); };