diff --git a/apps/web/src/components/repositories-settings-tab.tsx b/apps/web/src/components/repositories-settings-tab.tsx new file mode 100644 index 0000000..3b928de --- /dev/null +++ b/apps/web/src/components/repositories-settings-tab.tsx @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { apiClient } from "../api/client"; +import { GitRepository } from "../api/git_repositories"; + +export const RepositoriesSettingsTab: React.FC = () => { + const { projectId } = useParams<{ projectId: string }>(); + const [repositories, setRepositories] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(">"); + + useEffect(() => { + const fetchRepositories = async () => { + try { + const response = await apiClient.get( + `/projects/${projectId}/repositories` + ); + setRepositories(response.data); + } catch (err) { + setError("Failed to load repositories"); + } finally { + setLoading(false); + } + }; + + fetchRepositories(); + }, [projectId]); + + const handleDelete = async (repoId: string) => { + if (!window.confirm("Are you sure you want to delete this repository?")) return; + try { + await apiClient.delete(`/projects/${projectId}/repositories/${repoId}`); + setRepositories(repositories.filter((r) => r.id !== repoId)); + } catch (err) { + setError("Failed to delete repository"); + } + }; + + if (loading) return
Loading...
; + + return ( +
+

Repositories

+ {error &&
{error}
} + +
+ {repositories.length === 0 ? ( +

No repositories yet.

+ ) : ( + repositories.map((repo) => ( +
+
+

{repo.name}

+

{repo.remote_url}

+ + {repo.is_mirror ? "Mirror" : "Clone"} + +
+ +
+ )) + )} +
+
+ ); +}; diff --git a/apps/web/src/components/settings-tab-layout.tsx b/apps/web/src/components/settings-tab-layout.tsx new file mode 100644 index 0000000..b06e0a9 --- /dev/null +++ b/apps/web/src/components/settings-tab-layout.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { Link, useLocation } from "react-router-dom"; + +interface Tab { + id: string; + label: string; + path: string; +} + +interface SettingsTabLayoutProps { + tabs: Tab[]; + children: React.ReactNode; + basePath: string; +} + +export const SettingsTabLayout: React.FC = ({ + tabs, + children, + basePath, +}) => { + const location = useLocation(); + + return ( +
+ +
{children}
+
+ ); +}; diff --git a/apps/web/src/components/workspace-header.tsx b/apps/web/src/components/workspace-header.tsx new file mode 100644 index 0000000..4507af2 --- /dev/null +++ b/apps/web/src/components/workspace-header.tsx @@ -0,0 +1,45 @@ +import { Link } from "react-router-dom"; + +interface WorkspaceHeaderProps { + project: { + id: string; + name: string; + description?: string | null; + }; + currentRepo?: { + id: string; + name: string; + } | null; +} + +export const WorkspaceHeader = ({ project, currentRepo }: WorkspaceHeaderProps) => { + return ( +
+
+
📁
+
+

{project.name}

+ {currentRepo && ( + {currentRepo.name} + )} +
+
+
+ + 🕐 + History + + + ⚙️ + Settings + +
+
+ ); +}; diff --git a/apps/web/src/pages/project-settings.tsx b/apps/web/src/pages/project-settings.tsx new file mode 100644 index 0000000..3b5bb02 --- /dev/null +++ b/apps/web/src/pages/project-settings.tsx @@ -0,0 +1,148 @@ +import React, { useState, useEffect } from "react"; +import { useParams, Link, useNavigate, Routes, Route } from "react-router-dom"; +import { SettingsTabLayout } from "../components/settings-tab-layout"; +import { RepositoriesSettingsTab } from "../components/repositories-settings-tab"; +import { apiClient } from "../api/client"; +import { Project } from "../types"; + +const tabs = [ + { id: "general", label: "General", path: "general" }, + { id: "repositories", label: "Repositories", path: "repositories" }, + { id: "members", label: "Members", path: "members" }, +]; + +const GeneralSettings: React.FC<{ + project: Project; + projectId: string; +}> = ({ project, projectId }) => { + const navigate = useNavigate(); + const [name, setName] = useState(project.name); + const [description, setDescription] = useState(project.description || ""); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(""); + + const handleSave = async () => { + setSaving(true); + try { + await apiClient.patch(`/projects/${projectId}`, { + name, + description, + }); + } catch (err) { + setError("Failed to save changes"); + } finally { + setSaving(false); + } + }; + + const handleDelete = async () => { + if (!window.confirm("Are you sure you want to delete this project?")) return; + try { + await apiClient.delete(`/projects/${projectId}`); + navigate("/projects"); + } catch (err) { + setError("Failed to delete project"); + } + }; + + return ( +
+

General Settings

+ {error &&
{error}
} + +
+ + setName(e.target.value)} + className="form-input" + /> +
+ +
+ +