1import type { EnvironmentId } from "@t3tools/contracts";
2import {
3 readHostCredentialSettings,
4 sourceControlClients,
5} from "@t3tools/client-runtime/source-control-clients";
6import { ExternalLinkIcon } from "lucide-react";
7
8import { useEnvironmentSettings } from "../hooks/useSettings";
9import { cn } from "../lib/utils";
10import { useEnvironmentQuery } from "../state/query";
11import { sourceControlEnvironment } from "../state/sourceControl";
12import { T3AppIcon } from "./T3AppIcon";
13import { InlineButton } from "./ui/button";
14
15/**
16 * Says when the server's forge app sign-in cannot reach `repository`, so requests there fall back
17 * to the user's own login. Renders nothing for servers that do not use the app.
18 */
19export function SourceControlAppAccessNotice(props: {
20 readonly environmentId: EnvironmentId;
21 readonly provider: string | undefined;
22 readonly host: string | undefined;
23 /** `owner/name`. */
24 readonly repository: string;
25 readonly className?: string;
26}) {
27 const definition = props.provider ? sourceControlClients.find(props.provider) : undefined;
28 const appSignIn = definition?.credentials?.appSignIn;
29 const host = (props.host ?? definition?.publicHost ?? "").toLowerCase();
30 const signedIn = useEnvironmentSettings(props.environmentId, (settings) =>
31 definition === undefined
32 ? false
33 : (readHostCredentialSettings(settings, definition.kind).signIns[host] ?? "").length > 0,
34 );
35 const access = useEnvironmentQuery(
36 definition !== undefined && appSignIn?.hosts.includes(host) && signedIn
37 ? sourceControlEnvironment.appAccess({
38 environmentId: props.environmentId,
39 input: { provider: definition.kind, host, repository: props.repository },
40 })
41 : null,
42 );
43 if (appSignIn === undefined || access.data?.status !== "not-covered") return null;
44 const owner = props.repository.split("/")[0] ?? props.repository;
45
46 return (
47 <div
48 role="note"
49 className={cn(
50 "flex items-center gap-2.5 bg-muted/40 px-4 py-2 text-meta text-muted-foreground",
51 props.className,
52 )}
53 >
54 <T3AppIcon className="size-4 shrink-0" />
55 <p className="min-w-0 flex-1">
56 {appSignIn.appName} isn't installed on <span className="text-foreground">{owner}</span>, so
57 requests here use your own GitHub login and its lower rate limit.
58 </p>
59 <InlineButton
60 className="shrink-0"
61 render={<a href={appSignIn.installUrl} target="_blank" rel="noreferrer noopener" />}
62 >
63 Install on {owner}
64 <ExternalLinkIcon aria-hidden className="size-3" />
65 </InlineButton>
66 </div>
67 );
68}