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:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import {
|
||||
@@ -8,46 +8,35 @@ import {
|
||||
import type { GitRepository } from "../api/git_repositories";
|
||||
import { Icon } from "../components/icon";
|
||||
import { RepositoryCreateDialog } from "../components/repository-create-dialog";
|
||||
|
||||
type RepoStatus = "loading" | "ready" | "error";
|
||||
import { useAsyncData } from "../hooks/use-async-data";
|
||||
|
||||
export const GitRepositoriesPage = () => {
|
||||
const { projectId } = useParams<{ projectId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [status, setStatus] = useState<RepoStatus>("loading");
|
||||
const [repositories, setRepositories] = useState<GitRepository[]>([]);
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
||||
|
||||
const loadRepositories = useCallback(async () => {
|
||||
if (!projectId) return;
|
||||
setStatus("loading");
|
||||
try {
|
||||
const data = await listRepositories(projectId);
|
||||
setRepositories(data);
|
||||
setStatus("ready");
|
||||
} catch {
|
||||
setRepositories([]);
|
||||
setStatus("error");
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadRepositories();
|
||||
}, [loadRepositories]);
|
||||
const { data: repositories, status, reload } = useAsyncData<GitRepository[]>(
|
||||
async () => {
|
||||
if (!projectId) return [];
|
||||
return await listRepositories(projectId);
|
||||
},
|
||||
[projectId]
|
||||
);
|
||||
|
||||
const handleDelete = async (repoId: string) => {
|
||||
if (!projectId) return;
|
||||
try {
|
||||
await deleteRepository(projectId, repoId);
|
||||
setDeleteConfirmId(null);
|
||||
await loadRepositories();
|
||||
reload();
|
||||
} catch {
|
||||
setDeleteConfirmId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const isEmpty = status === "ready" && repositories.length === 0;
|
||||
const safeRepositories = repositories ?? [];
|
||||
const isEmpty = status === "ready" && safeRepositories.length === 0;
|
||||
|
||||
return (
|
||||
<section className="stack">
|
||||
@@ -64,7 +53,7 @@ export const GitRepositoriesPage = () => {
|
||||
{status === "error" && (
|
||||
<div className="card stack">
|
||||
<p>Failed to load repositories</p>
|
||||
<button className="secondary-button" onClick={() => void loadRepositories()} type="button">
|
||||
<button className="secondary-button" onClick={() => reload()} type="button">
|
||||
<Icon name="refresh" size="sm" />
|
||||
Retry
|
||||
</button>
|
||||
@@ -73,9 +62,9 @@ export const GitRepositoriesPage = () => {
|
||||
|
||||
{isEmpty && <p className="muted">No repositories yet. Create your first repository above.</p>}
|
||||
|
||||
{status === "ready" && repositories.length > 0 && (
|
||||
{status === "ready" && safeRepositories.length > 0 && (
|
||||
<div className="repository-list">
|
||||
{repositories.map((repo) => (
|
||||
{safeRepositories.map((repo) => (
|
||||
<article className="card repository-card" key={repo.id}>
|
||||
<div className="repository-info">
|
||||
<h3>{repo.name}</h3>
|
||||
@@ -131,7 +120,7 @@ export const GitRepositoriesPage = () => {
|
||||
open={showCreate}
|
||||
title="Create Repository"
|
||||
onClose={() => setShowCreate(false)}
|
||||
onCreated={loadRepositories}
|
||||
onCreated={reload}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user