|
| 1 | +import { IconBrandGithub, IconChevronRight } from "@tabler/icons-react"; |
| 2 | +import { useQuery } from "@tanstack/react-query"; |
| 3 | +import { Link, useNavigate } from "@tanstack/react-router"; |
| 4 | +import { useEffect, useState } from "react"; |
| 5 | +import { |
| 6 | + PageRows, |
| 7 | + PageSection, |
| 8 | + PageShell, |
| 9 | +} from "@/components/layout/page-shell"; |
| 10 | +import { GithubSources } from "@/components/settings/github-sources"; |
| 11 | +import { |
| 12 | + type LinkableProvider, |
| 13 | + LinkedAccountSection, |
| 14 | + linkableProviderTitle, |
| 15 | +} from "@/components/settings/linked-accounts"; |
| 16 | +import { |
| 17 | + linkedNotice, |
| 18 | + visibleLinkedNotice, |
| 19 | +} from "@/components/settings/linked-notice"; |
| 20 | +import { |
| 21 | + Item, |
| 22 | + ItemActions, |
| 23 | + ItemContent, |
| 24 | + ItemDescription, |
| 25 | + ItemMedia, |
| 26 | + ItemTitle, |
| 27 | +} from "@/components/ui/item"; |
| 28 | +import { IDENTITY_SUMMARIES, isBrokeredGithub } from "@/lib/connections/rows"; |
| 29 | +import { linkedAccountsQueryOptions } from "@/lib/identity/queries"; |
| 30 | +import { |
| 31 | + connectionsQueryOptions, |
| 32 | + personalConnections, |
| 33 | + pluginsPageQueryOptions, |
| 34 | +} from "@/lib/plugins/queries"; |
| 35 | + |
| 36 | +/** |
| 37 | + * GitHub's or Slack's page under Connections: the person's own account there, and for GitHub what |
| 38 | + * hangs off it. |
| 39 | + * |
| 40 | + * These used to be a "Linked accounts" section and a "GitHub events" section on the Connections |
| 41 | + * page itself, beside the apps, so a person had to know a link was a different kind of thing from an |
| 42 | + * app before they could find it. Now each is a row like any app, and this is the row's page. |
| 43 | + * |
| 44 | + * `linked` is what the GitHub callback sends back, `?linked=github` and its failures. It is said |
| 45 | + * once and then dropped from the address, so a reload does not repeat the news. |
| 46 | + */ |
| 47 | +export function IdentityConnectionPage({ |
| 48 | + linked, |
| 49 | + provider, |
| 50 | +}: { |
| 51 | + linked: string | undefined; |
| 52 | + provider: LinkableProvider; |
| 53 | +}) { |
| 54 | + const title = linkableProviderTitle(provider); |
| 55 | + const [arrival] = useState(() => linkedNotice(linked)); |
| 56 | + const links = useQuery(linkedAccountsQueryOptions()); |
| 57 | + const notice = visibleLinkedNotice(arrival, links.data); |
| 58 | + const navigate = useNavigate(); |
| 59 | + useEffect(() => { |
| 60 | + if (linked !== undefined) { |
| 61 | + navigate({ |
| 62 | + to: ".", |
| 63 | + search: (previous: Record<string, unknown>) => { |
| 64 | + const { linked: _linked, ...rest } = previous; |
| 65 | + return rest; |
| 66 | + }, |
| 67 | + replace: true, |
| 68 | + }); |
| 69 | + } |
| 70 | + }, [linked, navigate]); |
| 71 | + |
| 72 | + return ( |
| 73 | + <PageShell |
| 74 | + backButton={{ label: "Connections", linkProps: { to: "/connections" } }} |
| 75 | + description={IDENTITY_SUMMARIES[provider]} |
| 76 | + title={title} |
| 77 | + > |
| 78 | + {notice ? ( |
| 79 | + <p |
| 80 | + className={ |
| 81 | + notice.tone === "error" |
| 82 | + ? "mt-4 text-destructive text-sm" |
| 83 | + : "mt-4 text-muted-foreground text-sm" |
| 84 | + } |
| 85 | + role={notice.tone === "error" ? "alert" : "status"} |
| 86 | + > |
| 87 | + {notice.text} |
| 88 | + </p> |
| 89 | + ) : null} |
| 90 | + <LinkedAccountSection provider={provider} /> |
| 91 | + {provider === "github" ? ( |
| 92 | + <> |
| 93 | + <GithubSources /> |
| 94 | + <GithubTools /> |
| 95 | + </> |
| 96 | + ) : null} |
| 97 | + </PageShell> |
| 98 | + ); |
| 99 | +} |
| 100 | + |
| 101 | +/** |
| 102 | + * A brokered GitHub app, where one is enabled. It is a separate connection — tools that read and |
| 103 | + * change repositories as you — and the Connections list leaves its row out so GitHub is listed once, |
| 104 | + * which makes this the way to it. |
| 105 | + */ |
| 106 | +function GithubTools() { |
| 107 | + const plugins = useQuery(pluginsPageQueryOptions()); |
| 108 | + const connections = useQuery(connectionsQueryOptions()); |
| 109 | + const app = plugins.data?.composioConfigured |
| 110 | + ? plugins.data.servers.find( |
| 111 | + (server) => isBrokeredGithub(server) && server.accountMode !== "shared", |
| 112 | + ) |
| 113 | + : undefined; |
| 114 | + if (!app) return null; |
| 115 | + const connected = personalConnections( |
| 116 | + connections.data?.connections ?? [], |
| 117 | + ).some((row) => row.serverId === app.id); |
| 118 | + return ( |
| 119 | + <PageSection |
| 120 | + description="Lets your Bots read and change your repositories as you. A connection of its own, separate from your account above." |
| 121 | + title="Tools" |
| 122 | + > |
| 123 | + <PageRows> |
| 124 | + <Item |
| 125 | + render={<Link params={{ key: app.id }} to="/connections/$key" />} |
| 126 | + size="sm" |
| 127 | + > |
| 128 | + <ItemMedia variant="icon"> |
| 129 | + <IconBrandGithub /> |
| 130 | + </ItemMedia> |
| 131 | + <ItemContent> |
| 132 | + <ItemTitle>GitHub tools</ItemTitle> |
| 133 | + <ItemDescription> |
| 134 | + {connected ? "Connected" : "Not connected"} |
| 135 | + </ItemDescription> |
| 136 | + </ItemContent> |
| 137 | + <ItemActions> |
| 138 | + <IconChevronRight className="size-4 text-muted-foreground" /> |
| 139 | + </ItemActions> |
| 140 | + </Item> |
| 141 | + </PageRows> |
| 142 | + </PageSection> |
| 143 | + ); |
| 144 | +} |
0 commit comments