refactor: extract shared validation and reduce duplication

- Extract tool_types validation to shared module (validate_compose_yaml, check_port_exposed, validate_required_variables)
- Extract _get_user and _get_owned_project to auth/dependencies.py
- Create useAsyncData hook and apply to 6 pages
- Create extractErrorMessage utility
- TypeScript and build pass
This commit is contained in:
Alex Blank
2026-05-25 14:01:32 +02:00
parent a905cf729e
commit a37a3122f9
19 changed files with 327 additions and 442 deletions
+9 -17
View File
@@ -1,10 +1,9 @@
import { useCallback, useEffect, useState } from "react";
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 { Icon } from "../components/icon";
type SettingsStatus = "loading" | "ready" | "error";
import { useAsyncData } from "../hooks/use-async-data";
const TABS = [
{ label: "General", path: "general" },
@@ -26,7 +25,7 @@ type SettingsOutletContext = {
export const SettingsPage = () => {
const location = useLocation();
const [status, setStatus] = useState<SettingsStatus>("loading");
const { data: loadedConfig, status, reload } = useAsyncData<UserConfig>(getUserConfig, []);
const [config, setConfig] = useState<UserConfig>({
theme: "system",
default_editor: null,
@@ -36,19 +35,12 @@ export const SettingsPage = () => {
});
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle");
const loadConfig = useCallback(async () => {
try {
const data = await getUserConfig();
setConfig(data);
setStatus("ready");
} catch {
setStatus("error");
}
}, []);
// Sync loaded config into local editable state
useEffect(() => {
void loadConfig();
}, [loadConfig]);
if (loadedConfig) {
setConfig(loadedConfig);
}
}, [loadedConfig]);
const handleChange = (key: keyof UserConfigUpdate, value: string | null) => {
setConfig((prev) => ({ ...prev, [key]: value }));
@@ -86,7 +78,7 @@ export const SettingsPage = () => {
return (
<section className="stack">
<p>Failed to load settings</p>
<button className="secondary-button" onClick={() => void loadConfig()} type="button">
<button className="secondary-button" onClick={() => reload()} type="button">
<Icon name="refresh" size="sm" />
Retry
</button>