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
+18 -22
View File
@@ -1,39 +1,32 @@
import { useCallback, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { getCommitDetail, getRepositoryHistory, type CommitDetail, type CommitHistoryEntry } from "../api/git_repositories";
import { getCommitDetail, getRepositoryHistory, type CommitDetail, type CommitHistoryEntry, type CommitHistoryResponse } from "../api/git_repositories";
import { Icon } from "../components/icon";
import { useAsyncData } from "../hooks/use-async-data";
export const GitHistoryPage = () => {
const { projectId, repoId } = useParams<{ projectId: string; repoId: string }>();
const navigate = useNavigate();
const [commits, setCommits] = useState<CommitHistoryEntry[]>([]);
const [selectedCommit, setSelectedCommit] = useState<string | null>(null);
const [commitDetail, setCommitDetail] = useState<CommitDetail | null>(null);
const [branches, setBranches] = useState<string[]>([]);
const [selectedBranch, setSelectedBranch] = useState<string>("");
const [status, setStatus] = useState<"loading" | "ready" | "error">("loading");
const [detailStatus, setDetailStatus] = useState<"idle" | "loading" | "ready" | "error">("idle");
const loadHistory = useCallback(async () => {
if (!projectId || !repoId) return;
setStatus("loading");
try {
const data = await getRepositoryHistory(projectId, repoId, selectedBranch || undefined, 10000);
setCommits(data.commits);
setBranches(data.branches);
if (data.branches.length > 0 && !selectedBranch) {
setSelectedBranch(data.branches[0]);
}
setStatus("ready");
} catch {
setStatus("error");
}
}, [projectId, repoId, selectedBranch]);
const { data: historyData, status, reload } = useAsyncData<CommitHistoryResponse>(
async () => {
if (!projectId || !repoId) return { commits: [], branches: [], tags: [] };
return await getRepositoryHistory(projectId, repoId, selectedBranch || undefined, 10000);
},
[projectId, repoId, selectedBranch]
);
// Auto-select first branch when data loads
useEffect(() => {
void loadHistory();
}, [loadHistory]);
if (historyData?.branches.length && !selectedBranch) {
setSelectedBranch(historyData.branches[0]);
}
}, [historyData?.branches, selectedBranch]);
const handleCommitClick = async (hash: string) => {
if (!projectId || !repoId) return;
@@ -70,7 +63,7 @@ export const GitHistoryPage = () => {
return (
<section className="stack">
<p>Failed to load commit history</p>
<button className="secondary-button" onClick={() => void loadHistory()} type="button">
<button className="secondary-button" onClick={() => reload()} type="button">
<Icon name="refresh" size="sm" />
Retry
</button>
@@ -78,6 +71,9 @@ export const GitHistoryPage = () => {
);
}
const commits = historyData?.commits ?? [];
const branches = historyData?.branches ?? [];
return (
<section className="stack">
<div className="page-header">