mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-30 16:20:35 +02:00
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
116 lines
5.0 KiB
TypeScript
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>
|
|
);
|
|
}
|