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 { Link } from "react-router-dom";
|
||||
|
||||
@@ -11,14 +11,13 @@ import {
|
||||
type ProjectUpdateInput,
|
||||
} from "../api/projects";
|
||||
import { Icon } from "../components/icon";
|
||||
import { useAsyncData } from "../hooks/use-async-data";
|
||||
import type { Project } from "../types";
|
||||
|
||||
type ProjectsStatus = "loading" | "ready" | "error";
|
||||
type DialogMode = "none" | "create" | "edit";
|
||||
|
||||
export const ProjectsPage = () => {
|
||||
const [status, setStatus] = useState<ProjectsStatus>("loading");
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const { data: projects, status, reload } = useAsyncData<Project[]>(listProjects, []);
|
||||
const [dialogMode, setDialogMode] = useState<DialogMode>("none");
|
||||
const [editingProject, setEditingProject] = useState<Project | null>(null);
|
||||
const [formName, setFormName] = useState("");
|
||||
@@ -26,21 +25,7 @@ export const ProjectsPage = () => {
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
||||
|
||||
const loadProjects = useCallback(async () => {
|
||||
setStatus("loading");
|
||||
try {
|
||||
const data = await listProjects();
|
||||
setProjects(data);
|
||||
setStatus("ready");
|
||||
} catch {
|
||||
setProjects([]);
|
||||
setStatus("error");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void loadProjects();
|
||||
}, [loadProjects]);
|
||||
const safeProjects = projects ?? [];
|
||||
|
||||
const openCreate = () => {
|
||||
setFormName("");
|
||||
@@ -88,7 +73,7 @@ export const ProjectsPage = () => {
|
||||
await updateProject(editingProject.id, input);
|
||||
}
|
||||
closeDialog();
|
||||
await loadProjects();
|
||||
reload();
|
||||
} catch {
|
||||
setFormError("Failed to save project");
|
||||
}
|
||||
@@ -98,13 +83,13 @@ export const ProjectsPage = () => {
|
||||
try {
|
||||
await deleteProject(projectId);
|
||||
setDeleteConfirmId(null);
|
||||
await loadProjects();
|
||||
reload();
|
||||
} catch {
|
||||
setDeleteConfirmId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const isEmpty = status === "ready" && projects.length === 0;
|
||||
const isEmpty = status === "ready" && safeProjects.length === 0;
|
||||
|
||||
return (
|
||||
<section className="stack">
|
||||
@@ -121,7 +106,7 @@ export const ProjectsPage = () => {
|
||||
{status === "error" && (
|
||||
<div className="card stack">
|
||||
<p>Failed to load projects</p>
|
||||
<button className="secondary-button" onClick={() => void loadProjects()} type="button">
|
||||
<button className="secondary-button" onClick={() => reload()} type="button">
|
||||
<Icon name="refresh" size="sm" />
|
||||
Retry
|
||||
</button>
|
||||
@@ -130,9 +115,9 @@ export const ProjectsPage = () => {
|
||||
|
||||
{isEmpty && <p className="muted">No projects yet. Create your first project above.</p>}
|
||||
|
||||
{status === "ready" && projects.length > 0 && (
|
||||
{status === "ready" && safeProjects.length > 0 && (
|
||||
<div className="project-list">
|
||||
{projects.map((project) => (
|
||||
{safeProjects.map((project) => (
|
||||
<article className="card project-card" key={project.id}>
|
||||
<div className="project-info">
|
||||
<h3>{project.name}</h3>
|
||||
|
||||
Reference in New Issue
Block a user