From bf713d3ad5f7bc56e3cc6831d8b5d7578c342360 Mon Sep 17 00:00:00 2001 From: yushen Date: Thu, 2 Apr 2026 12:14:59 +0800 Subject: [PATCH] feat(web): extract Repositories into standalone settings tab Move the Repositories section from the General workspace settings page into its own dedicated tab in the Settings sidebar, making it a first-class entry alongside General and Members. This reduces the navigation depth from 3 clicks + scroll to a single click. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../settings/_components/repositories-tab.tsx | 129 ++++++++++++++++++ .../settings/_components/workspace-tab.tsx | 81 +---------- apps/web/app/(dashboard)/settings/page.tsx | 5 +- 3 files changed, 134 insertions(+), 81 deletions(-) create mode 100644 apps/web/app/(dashboard)/settings/_components/repositories-tab.tsx diff --git a/apps/web/app/(dashboard)/settings/_components/repositories-tab.tsx b/apps/web/app/(dashboard)/settings/_components/repositories-tab.tsx new file mode 100644 index 00000000..4b352bd3 --- /dev/null +++ b/apps/web/app/(dashboard)/settings/_components/repositories-tab.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Save, Plus, Trash2 } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { toast } from "sonner"; +import { useAuthStore } from "@/features/auth"; +import { useWorkspaceStore } from "@/features/workspace"; +import { api } from "@/shared/api"; +import type { WorkspaceRepo } from "@/shared/types"; + +export function RepositoriesTab() { + const user = useAuthStore((s) => s.user); + const workspace = useWorkspaceStore((s) => s.workspace); + const members = useWorkspaceStore((s) => s.members); + const updateWorkspace = useWorkspaceStore((s) => s.updateWorkspace); + + const [repos, setRepos] = useState(workspace?.repos ?? []); + const [saving, setSaving] = useState(false); + + const currentMember = members.find((m) => m.user_id === user?.id) ?? null; + const canManageWorkspace = currentMember?.role === "owner" || currentMember?.role === "admin"; + + useEffect(() => { + setRepos(workspace?.repos ?? []); + }, [workspace]); + + const handleSave = async () => { + if (!workspace) return; + setSaving(true); + try { + const updated = await api.updateWorkspace(workspace.id, { repos }); + updateWorkspace(updated); + toast.success("Repositories saved"); + } catch (e) { + toast.error(e instanceof Error ? e.message : "Failed to save repositories"); + } finally { + setSaving(false); + } + }; + + const handleAddRepo = () => { + setRepos([...repos, { url: "", description: "" }]); + }; + + const handleRemoveRepo = (index: number) => { + setRepos(repos.filter((_, i) => i !== index)); + }; + + const handleRepoChange = (index: number, field: keyof WorkspaceRepo, value: string) => { + setRepos(repos.map((r, i) => (i === index ? { ...r, [field]: value } : r))); + }; + + if (!workspace) return null; + + return ( +
+
+

Repositories

+ + + +

+ GitHub repositories associated with this workspace. Agents use these to clone and work on code. +

+ + {repos.map((repo, index) => ( +
+
+ handleRepoChange(index, "url", e.target.value)} + disabled={!canManageWorkspace} + placeholder="https://github.com/org/repo" + className="text-sm" + /> + handleRepoChange(index, "description", e.target.value)} + disabled={!canManageWorkspace} + placeholder="Description (e.g. Go backend + Next.js frontend)" + className="text-sm" + /> +
+ {canManageWorkspace && ( + + )} +
+ ))} + + {canManageWorkspace && ( +
+ + +
+ )} + + {!canManageWorkspace && ( +

+ Only admins and owners can manage repositories. +

+ )} +
+
+
+
+ ); +} diff --git a/apps/web/app/(dashboard)/settings/_components/workspace-tab.tsx b/apps/web/app/(dashboard)/settings/_components/workspace-tab.tsx index 3074d270..594e8c45 100644 --- a/apps/web/app/(dashboard)/settings/_components/workspace-tab.tsx +++ b/apps/web/app/(dashboard)/settings/_components/workspace-tab.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { Save, LogOut, Plus, Trash2 } from "lucide-react"; +import { Save, LogOut } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; @@ -21,7 +21,6 @@ import { toast } from "sonner"; import { useAuthStore } from "@/features/auth"; import { useWorkspaceStore } from "@/features/workspace"; import { api } from "@/shared/api"; -import type { WorkspaceRepo } from "@/shared/types"; export function WorkspaceTab() { const user = useAuthStore((s) => s.user); @@ -34,7 +33,6 @@ export function WorkspaceTab() { const [name, setName] = useState(workspace?.name ?? ""); const [description, setDescription] = useState(workspace?.description ?? ""); const [context, setContext] = useState(workspace?.context ?? ""); - const [repos, setRepos] = useState(workspace?.repos ?? []); const [saving, setSaving] = useState(false); const [actionId, setActionId] = useState(null); const [confirmAction, setConfirmAction] = useState<{ @@ -52,7 +50,6 @@ export function WorkspaceTab() { setName(workspace?.name ?? ""); setDescription(workspace?.description ?? ""); setContext(workspace?.context ?? ""); - setRepos(workspace?.repos ?? []); }, [workspace]); const handleSave = async () => { @@ -63,7 +60,6 @@ export function WorkspaceTab() { name, description, context, - repos, }); updateWorkspace(updated); toast.success("Workspace settings saved"); @@ -74,18 +70,6 @@ export function WorkspaceTab() { } }; - const handleAddRepo = () => { - setRepos([...repos, { url: "", description: "" }]); - }; - - const handleRemoveRepo = (index: number) => { - setRepos(repos.filter((_, i) => i !== index)); - }; - - const handleRepoChange = (index: number, field: keyof WorkspaceRepo, value: string) => { - setRepos(repos.map((r, i) => (i === index ? { ...r, [field]: value } : r))); - }; - const handleLeaveWorkspace = () => { if (!workspace) return; setConfirmAction({ @@ -191,69 +175,6 @@ export function WorkspaceTab() { - {/* Repositories */} -
-

Repositories

- - - -

- GitHub repositories associated with this workspace. Agents use these to clone and work on code. -

- - {repos.map((repo, index) => ( -
-
- handleRepoChange(index, "url", e.target.value)} - disabled={!canManageWorkspace} - placeholder="https://github.com/org/repo" - className="text-sm" - /> - handleRepoChange(index, "description", e.target.value)} - disabled={!canManageWorkspace} - placeholder="Description (e.g. Go backend + Next.js frontend)" - className="text-sm" - /> -
- {canManageWorkspace && ( - - )} -
- ))} - - {canManageWorkspace && ( -
- - -
- )} -
-
-
- {/* Danger Zone */}
diff --git a/apps/web/app/(dashboard)/settings/page.tsx b/apps/web/app/(dashboard)/settings/page.tsx index 6e0b2cc5..7042ba80 100644 --- a/apps/web/app/(dashboard)/settings/page.tsx +++ b/apps/web/app/(dashboard)/settings/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { User, Palette, Key, Settings, Users } from "lucide-react"; +import { User, Palette, Key, Settings, Users, FolderGit2 } from "lucide-react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { useWorkspaceStore } from "@/features/workspace"; import { AccountTab } from "./_components/account-tab"; @@ -8,6 +8,7 @@ import { AppearanceTab } from "./_components/general-tab"; import { TokensTab } from "./_components/tokens-tab"; import { WorkspaceTab } from "./_components/workspace-tab"; import { MembersTab } from "./_components/members-tab"; +import { RepositoriesTab } from "./_components/repositories-tab"; const accountTabs = [ { value: "profile", label: "Profile", icon: User }, @@ -17,6 +18,7 @@ const accountTabs = [ const workspaceTabs = [ { value: "workspace", label: "General", icon: Settings }, + { value: "repositories", label: "Repositories", icon: FolderGit2 }, { value: "members", label: "Members", icon: Users }, ]; @@ -60,6 +62,7 @@ export default function SettingsPage() { +