Compare commits

...

1 Commits

Author SHA1 Message Date
Jiang Bohan
51f09151ae refactor(integrations): drop installation list from Settings tab
The card displayed a per-installation row (avatar + account_login +
"User|Organization · connected <date>") plus a disconnect button. In
practice the title regularly fell back to "unknown" because the server's
fetchInstallationAccount call doesn't sign App JWT, and the
account-level framing also leaked GitHub's data model into the UX —
users care about which repos are wired up, not which GitHub account the
App is installed on.

Collapse the card to: GitHub mark + description + Connect button (plus
the "not configured" hint and role gate). Existing installations stay
fully manageable from GitHub's own settings page, reachable via Connect.

Removes:
- installation list + disconnect button + handleDisconnect
- useQueryClient / Trash2 / githubKeys imports
- five now-dead i18n keys (loading / empty / connected_at /
  toast_disconnected / toast_disconnect_failed) in en + zh-Hans
2026-05-12 14:55:30 +08:00
3 changed files with 8 additions and 80 deletions

View File

@@ -167,14 +167,9 @@
"connect_disabled_tooltip": "GitHub App is not configured on this server",
"not_configured": "GitHub integration is not configured for this deployment. Operators must set",
"not_configured_and": "and",
"loading": "Loading…",
"empty": "No GitHub accounts connected yet.",
"connected_at": "{{type}} · connected {{date}}",
"manage_hint": "Only admins and owners can manage integrations.",
"toast_not_configured": "GitHub integration is not configured for this deployment",
"toast_open_failed": "Failed to open GitHub install",
"toast_disconnected": "Disconnected GitHub account",
"toast_disconnect_failed": "Failed to disconnect"
"toast_open_failed": "Failed to open GitHub install"
},
"repositories": {
"section_title": "Repositories",

View File

@@ -167,14 +167,9 @@
"connect_disabled_tooltip": "服务端未配置 GitHub App",
"not_configured": "当前部署没有配置 GitHub 集成。运维需要设置",
"not_configured_and": "和",
"loading": "加载中…",
"empty": "还没有连接 GitHub 账户。",
"connected_at": "{{type}} · {{date}} 连接",
"manage_hint": "只有管理员和所有者可以管理集成。",
"toast_not_configured": "当前部署未配置 GitHub 集成",
"toast_open_failed": "打开 GitHub 安装页失败",
"toast_disconnected": "已断开 GitHub 账户",
"toast_disconnect_failed": "断开连接失败"
"toast_open_failed": "打开 GitHub 安装页失败"
},
"repositories": {
"section_title": "代码仓库",

View File

@@ -1,15 +1,14 @@
"use client";
import { useState } from "react";
import { Trash2 } from "lucide-react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import { Button } from "@multica/ui/components/ui/button";
import { Card, CardContent } from "@multica/ui/components/ui/card";
import { useAuthStore } from "@multica/core/auth";
import { useWorkspaceId } from "@multica/core/hooks";
import { memberListOptions } from "@multica/core/workspace/queries";
import { githubInstallationsOptions, githubKeys } from "@multica/core/github/queries";
import { githubInstallationsOptions } from "@multica/core/github/queries";
import { api } from "@multica/core/api";
import { useT } from "../../i18n";
@@ -27,21 +26,19 @@ export function IntegrationsTab() {
const { t } = useT("settings");
const wsId = useWorkspaceId();
const user = useAuthStore((s) => s.user);
const qc = useQueryClient();
const { data: members = [] } = useQuery(memberListOptions(wsId));
const [connecting, setConnecting] = useState(false);
const currentMember = members.find((m) => m.user_id === user?.id) ?? null;
const canManage = currentMember?.role === "owner" || currentMember?.role === "admin";
// The list endpoint is admin-only; non-admins would see a 403 toast and
// an empty configured state. Gate the query on canManage so members get
// a clean read-only view.
const { data, isLoading } = useQuery({
// Only used to gate the Connect button + show a "not configured" hint;
// we no longer render the installation list here — admins manage existing
// installations on GitHub directly via the Connect flow.
const { data } = useQuery({
...githubInstallationsOptions(wsId),
enabled: !!wsId && canManage,
});
const installations = data?.installations ?? [];
const configured = data?.configured ?? false;
async function handleConnect() {
@@ -60,16 +57,6 @@ export function IntegrationsTab() {
}
}
async function handleDisconnect(installationDbId: string) {
try {
await api.deleteGitHubInstallation(wsId, installationDbId);
qc.invalidateQueries({ queryKey: githubKeys.installations(wsId) });
toast.success(t(($) => $.integrations.toast_disconnected));
} catch (e) {
toast.error(e instanceof Error ? e.message : t(($) => $.integrations.toast_disconnect_failed));
}
}
return (
<div className="space-y-8">
<section className="space-y-4">
@@ -115,55 +102,6 @@ export function IntegrationsTab() {
</p>
)}
{canManage && configured && (
<div className="space-y-2">
{isLoading && <p className="text-xs text-muted-foreground">{t(($) => $.integrations.loading)}</p>}
{!isLoading && installations.length === 0 && (
<p className="text-xs text-muted-foreground">
{t(($) => $.integrations.empty)}
</p>
)}
{installations.map((inst) => (
<div
key={inst.id}
className="flex items-center justify-between gap-3 rounded-md border px-3 py-2"
>
<div className="flex items-center gap-2 min-w-0">
{inst.account_avatar_url && (
<img
src={inst.account_avatar_url}
alt=""
className="h-6 w-6 rounded-full shrink-0"
/>
)}
<div className="min-w-0">
<p className="text-sm font-medium truncate">{inst.account_login}</p>
<p className="text-xs text-muted-foreground">
{t(($) => $.integrations.connected_at, {
type: inst.account_type,
date: new Date(inst.created_at).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
}),
})}
</p>
</div>
</div>
{canManage && (
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-destructive shrink-0"
onClick={() => handleDisconnect(inst.id)}
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
)}
</div>
))}
</div>
)}
{!canManage && (
<p className="text-xs text-muted-foreground">
{t(($) => $.integrations.manage_hint)}