import { GitPullRequestIcon, RefreshCwIcon } from "lucide-react"; import { Option } from "effect"; import { type ReactNode, useId } from "react"; import type { SourceControlProviderKind, SourceControlDiscoveryResult, SourceControlProviderAuth, SourceControlProviderDiscoveryItem, VcsDiscoveryItem, } from "@t3tools/contracts"; import { cn } from "../../lib/utils"; import { refreshSourceControlDiscovery, useSourceControlDiscovery, } from "../../lib/sourceControlDiscoveryState"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from "../ui/empty"; import { Skeleton } from "../ui/skeleton"; import { Switch } from "../ui/switch"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { GitHubIcon, type Icon } from "../Icons"; import { RedactedSensitiveText } from "./RedactedSensitiveText"; import { SettingsPageContainer, SettingsSection } from "./settingsLayout"; const EMPTY_DISCOVERY_RESULT: SourceControlDiscoveryResult = { versionControlSystems: [], sourceControlProviders: [], }; const GitLabIcon: Icon = (props) => ( ); const AzureDevOpsIcon: Icon = (props) => { const id = useId().replaceAll(":", ""); const gradientA = `${id}-azure-a`; const gradientB = `${id}-azure-b`; const gradientC = `${id}-azure-c`; return ( ); }; const BitbucketIcon: Icon = (props) => { const id = useId().replaceAll(":", ""); const gradientId = `${id}-bitbucket-a`; return ( ); }; const SOURCE_CONTROL_PROVIDER_ICONS: Partial> = { github: GitHubIcon, gitlab: GitLabIcon, "azure-devops": AzureDevOpsIcon, bitbucket: BitbucketIcon, }; function optionLabel(value: Option.Option): string | null { return Option.getOrNull(value); } function isProviderDiscoveryItem( item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem, ): item is SourceControlProviderDiscoveryItem { return "auth" in item; } function authPresentation(auth: SourceControlProviderAuth): { readonly label: string; readonly badge: "warning" | null; } { if (auth.status === "authenticated") { return { label: "Signed in", badge: null }; } if (auth.status === "unauthenticated") { return { label: "Sign in", badge: "warning" }; } return { label: "Sign in", badge: null }; } function RedactedAccount(props: { readonly account: string | null }) { return ( ); } function itemStatusDot(item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem): string { if (!item.implemented) return "bg-muted-foreground/35"; if (item.status !== "available") return "bg-warning"; if (isProviderDiscoveryItem(item) && item.auth.status !== "authenticated") return "bg-warning"; return "bg-success"; } function SourceControlItemMark({ item, }: { readonly item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem; }) { const dotClassName = itemStatusDot(item); const Icon = isProviderDiscoveryItem(item) ? SOURCE_CONTROL_PROVIDER_ICONS[item.kind] : null; if (!Icon) { return ; } return ( ); } function itemSummary({ item, auth, authAccount, }: { readonly item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem; readonly auth: SourceControlProviderAuth | null; readonly authAccount: string | null; }) { if (!item.implemented) { return Support for {item.label} is coming soon.; } if (item.status !== "available") { return Not found - {item.installHint}; } if (auth) { if (auth.status === "authenticated") { return ( <> Authenticated {authAccount ? ( <> as > ) : null} > ); } if (auth.status === "unauthenticated") { return Sign in with the {item.executable} CLI to enable pull request actions.; } return ( Install and sign in with the {item.executable} CLI to enable pull request actions. ); } return Available; } function DiscoveryItemRow({ item, }: { readonly item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem; }) { const version = optionLabel(item.version); const enabled = item.implemented && item.status === "available"; const auth = isProviderDiscoveryItem(item) ? item.auth : null; const authStatus = auth ? authPresentation(auth) : null; const authAccount = auth ? optionLabel(auth.account) : null; return ( {item.label} {version ? {version} : null} {!item.implemented ? ( Coming Soon ) : null} {authStatus?.badge ? ( {authStatus.label} ) : null} {itemSummary({ item, auth, authAccount })} {item.implemented ? ( ) : null} ); } function SourceControlSectionSkeleton({ title, headerAction, }: { readonly title: string; readonly headerAction?: ReactNode; }) { return ( {Array.from({ length: 2 }, (_, index) => ( ))} ); } function EmptySourceControlDiscovery({ error, isPending, onScan, }: { readonly error: string | null; readonly isPending: boolean; readonly onScan: () => void; }) { const hasError = error !== null; return ( {hasError ? "Could not scan source control" : "No source control tools found"} {hasError ? error : "Install a supported Git or pull request CLI, then scan again."} Scan ); } export function SourceControlSettingsPanel() { const discovery = useSourceControlDiscovery(); const result = discovery.data ?? EMPTY_DISCOVERY_RESULT; const hasDiscoveryItems = result.versionControlSystems.length > 0 || result.sourceControlProviders.length > 0; const isInitialScanPending = discovery.isPending && discovery.data === null; const handleScan = () => { void refreshSourceControlDiscovery(); }; const scanButton = ( } /> Scan source control tools ); return ( {isInitialScanPending ? ( <> > ) : hasDiscoveryItems ? ( <> {result.versionControlSystems.length > 0 ? ( {result.versionControlSystems.map((item) => ( ))} ) : null} {result.sourceControlProviders.length > 0 ? ( {result.sourceControlProviders.map((item) => ( ))} ) : null} > ) : ( )} ); }
{version}
{itemSummary({ item, auth, authAccount })}