"use client"; import * as React from "react"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { GitBranch, Loader2, Plus, X } from "lucide-react"; import { useParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { api } from "@/lib/trpc"; import type { ForgeType } from "@tasks/shared/connected-repos"; const FORGE_LABELS: Record = { github: "GitHub", forgejo: "Forgejo / Gitea", gitlab: "GitLab", bitbucket: "Bitbucket", }; type ConnectForm = { remoteUrl: string; displayName: string; tokenEnvVar: string; }; const EMPTY_FORM: ConnectForm = { remoteUrl: "", displayName: "", tokenEnvVar: "", }; export default function ConnectedReposSettingsPage() { const params = useParams(); const workspaceSlug = params?.workspaceSlug as string | undefined; const utils = api.useUtils(); const listQuery = api.connectedRepos.list.useQuery( { workspace: workspaceSlug ?? "" }, { enabled: Boolean(workspaceSlug) }, ); const [dialogOpen, setDialogOpen] = React.useState(false); const [form, setForm] = React.useState(EMPTY_FORM); const [parsedPreview, setParsedPreview] = React.useState<{ forgeType: ForgeType; owner: string; repoName: string; apiBaseUrl: string; } | null>(null); const [testResult, setTestResult] = React.useState>({}); const parseMut = api.connectedRepos.parseRemote.useMutation({ onSuccess: (parsed) => { setParsedPreview({ forgeType: parsed.forgeType, owner: parsed.owner, repoName: parsed.repoName, apiBaseUrl: parsed.apiBaseUrl, }); setForm((prev) => ({ ...prev, displayName: prev.displayName || `${parsed.owner}/${parsed.repoName}`, })); }, onError: () => setParsedPreview(null), }); const connectMut = api.connectedRepos.connect.useMutation({ onSuccess: () => { void utils.connectedRepos.list.invalidate(); setDialogOpen(false); setForm(EMPTY_FORM); setParsedPreview(null); }, }); const disconnectMut = api.connectedRepos.disconnect.useMutation({ onSuccess: () => void utils.connectedRepos.list.invalidate(), }); const testMut = api.connectedRepos.testConnection.useMutation({ onSuccess: (result, vars) => { setTestResult((prev) => ({ ...prev, [vars.repoId]: result.ok ? `OK — default branch ${result.defaultBranch} (${result.latencyMs}ms)` : `Failed: ${result.error ?? "unknown"}`, })); if (result.ok) { void utils.connectedRepos.list.invalidate(); } }, }); const canManage = listQuery.data?.canManage ?? false; const repos = listQuery.data?.repos ?? []; const openCreate = () => { setForm(EMPTY_FORM); setParsedPreview(null); setDialogOpen(true); }; const handleParse = () => { if (!workspaceSlug || !form.remoteUrl.trim()) { return; } parseMut.mutate({ workspace: workspaceSlug, remoteUrl: form.remoteUrl.trim() }); }; const handleConnect = () => { if (!workspaceSlug || !form.remoteUrl.trim()) { return; } connectMut.mutate({ workspace: workspaceSlug, remoteUrl: form.remoteUrl.trim(), displayName: form.displayName.trim() || undefined, tokenEnvVar: form.tokenEnvVar.trim() || null, }); }; return (

Connected repos

Paste a git remote URL to link Forgejo, GitHub, or other forges. API tokens are referenced by env var name — never stored in the database.

{canManage && ( )}
{listQuery.isLoading ? (
Loading repos…
) : repos.length === 0 ? (
No connected repos yet. {canManage && " Connect one to prepare for orchestrator and worker automation."}
) : (
    {repos.map((repo) => (
  • {repo.displayName} {FORGE_LABELS[repo.forgeType]}

    {repo.gitRemoteUrl}

    {repo.owner}/{repo.repoName} {repo.defaultBranch ? ` · ${repo.defaultBranch}` : ""} {repo.tokenEnvVar ? ` · token: ${repo.tokenEnvVar}` : ""}

    {testResult[repo.id] && (

    {testResult[repo.id]}

    )}
    {canManage && (
    )}
  • ))}
)}
Connect repository
setForm((f) => ({ ...f, remoteUrl: e.target.value }))} />
{parseMut.error && (

{parseMut.error.message}

)}
{parsedPreview && (

Detected{" "} {FORGE_LABELS[parsedPreview.forgeType]} —{" "} {parsedPreview.owner}/{parsedPreview.repoName}

{parsedPreview.apiBaseUrl}

)}
setForm((f) => ({ ...f, displayName: e.target.value }))} />
setForm((f) => ({ ...f, tokenEnvVar: e.target.value }))} />
{connectMut.error && (

{connectMut.error.message}

)}
); }