Files
multica/packages/views/settings/components/integrations-tab.tsx
Bohan Jiang f17acc21de refactor(integrations): drop installation list from Settings tab (#2468)
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 15:04:41 +08:00

116 lines
5.0 KiB
TypeScript

"use client";
import { useState } from "react";
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 } from "@multica/core/github/queries";
import { api } from "@multica/core/api";
import { useT } from "../../i18n";
// lucide-react v1.x dropped brand marks (including Github). Render an inline
// SVG of the official GitHub octocat mark so the card is still recognizable.
function GitHubMark({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" className={className} fill="currentColor">
<path d="M12 .5C5.6.5.5 5.6.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.2.8-.6v-2.2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.3-1.3-1.7-1.3-1.7-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2.9-.3 1.9-.4 2.9-.4s2 .1 2.9.4c2.3-1.5 3.3-1.2 3.3-1.2.6 1.6.2 2.8.1 3.1.7.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.6 18.4.5 12 .5z" />
</svg>
);
}
export function IntegrationsTab() {
const { t } = useT("settings");
const wsId = useWorkspaceId();
const user = useAuthStore((s) => s.user);
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";
// 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 configured = data?.configured ?? false;
async function handleConnect() {
setConnecting(true);
try {
const resp = await api.getGitHubConnectURL(wsId);
if (!resp.configured || !resp.url) {
toast.error(t(($) => $.integrations.toast_not_configured));
return;
}
window.open(resp.url, "_blank", "noopener");
} catch (e) {
toast.error(e instanceof Error ? e.message : t(($) => $.integrations.toast_open_failed));
} finally {
setConnecting(false);
}
}
return (
<div className="space-y-8">
<section className="space-y-4">
<h2 className="text-sm font-semibold">{t(($) => $.integrations.section_title)}</h2>
<Card>
<CardContent className="space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3">
<GitHubMark className="h-6 w-6 mt-0.5 shrink-0" />
<div className="space-y-1">
<p className="text-sm font-medium">{t(($) => $.integrations.github_title)}</p>
<p className="text-xs text-muted-foreground">
{t(($) => $.integrations.github_description_prefix)}{" "}
<code className="rounded bg-muted px-1 py-0.5 text-[10px]">
{t(($) => $.integrations.github_identifier_example)}
</code>{" "}
{t(($) => $.integrations.github_description_suffix)}{" "}
<strong>{t(($) => $.integrations.github_description_done)}</strong>.
</p>
</div>
</div>
{canManage && (
<Button
size="sm"
onClick={handleConnect}
disabled={connecting || !configured}
title={!configured ? t(($) => $.integrations.connect_disabled_tooltip) : undefined}
>
{connecting
? t(($) => $.integrations.connect_opening)
: t(($) => $.integrations.connect_github)}
</Button>
)}
</div>
{canManage && !configured && (
<p className="text-xs text-muted-foreground">
{t(($) => $.integrations.not_configured)}{" "}
<code className="rounded bg-muted px-1 py-0.5 text-[10px]">GITHUB_APP_SLUG</code>{" "}
{t(($) => $.integrations.not_configured_and)}{" "}
<code className="rounded bg-muted px-1 py-0.5 text-[10px]">GITHUB_WEBHOOK_SECRET</code>.
</p>
)}
{!canManage && (
<p className="text-xs text-muted-foreground">
{t(($) => $.integrations.manage_hint)}
</p>
)}
</CardContent>
</Card>
</section>
</div>
);
}