import { GitPullRequestIcon, RefreshCwIcon } from "lucide-react"; import { Option } from "effect"; import { type ReactNode } from "react"; import type { SourceControlProviderKind, SourceControlDiscoveryResult, SourceControlProviderAuth, SourceControlProviderDiscoveryItem, VcsDriverKind, 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 { AzureDevOpsIcon, BitbucketIcon, GitHubIcon, GitIcon, GitLabIcon, JujutsuIcon, type Icon, } from "../Icons"; import { RedactedSensitiveText } from "./RedactedSensitiveText"; import { SettingsPageContainer, SettingsSection } from "./settingsLayout"; const EMPTY_DISCOVERY_RESULT: SourceControlDiscoveryResult = { versionControlSystems: [], sourceControlProviders: [], }; const SOURCE_CONTROL_PROVIDER_ICONS: Partial> = { github: GitHubIcon, gitlab: GitLabIcon, "azure-devops": AzureDevOpsIcon, bitbucket: BitbucketIcon, }; const VCS_ICONS: Partial> = { git: GitIcon, jj: JujutsuIcon, }; const SOURCE_CONTROL_SKELETON_ROWS = ["primary", "secondary"] as const; function optionLabel(value: Option.Option): string | null { return Option.getOrNull(value); } function isProviderDiscoveryItem( item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem, ): item is SourceControlProviderDiscoveryItem { return "auth" in item; } function isVcsNotReady(item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem): boolean { return !isProviderDiscoveryItem(item) && !item.implemented; } function authPresentation(auth: SourceControlProviderAuth): { readonly label: string; readonly badge: "warning" | null; } { if (auth.status === "authenticated") { return { label: "Authenticated", badge: null }; } if (auth.status === "unauthenticated") { return { label: "Not authenticated", badge: "warning" }; } return { label: "Status unknown", badge: null }; } function RedactedAccount(props: { readonly account: string | null }) { return ( ); } function itemStatusDot(item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem): string { if (isVcsNotReady(item)) 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] : VCS_ICONS[item.kind]; if (!Icon) { return ; } return ( ); } function itemSummary({ item, auth, authAccount, }: { readonly item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem; readonly auth: SourceControlProviderAuth | null; readonly authAccount: string | null; }) { if (isVcsNotReady(item)) { return Support for {item.label} is coming soon.; } if (item.status !== "available") { return Not available on this server — {item.installHint}; } if (auth) { if (auth.status === "authenticated") { return ( <> Authenticated {authAccount ? ( <> as ) : null} ); } if (!item.executable) { return {item.installHint}; } if (auth.status === "unauthenticated") { return ( {item.label} is not authenticated on this server. Sign in or configure credentials using the {item.executable}{" "} tool on the server host to enable pull request features. ); } return ( Could not verify {item.label}. {item.installHint} ); } return Available; } function DiscoveryItemRow({ item, }: { readonly item: VcsDiscoveryItem | SourceControlProviderDiscoveryItem; }) { const version = optionLabel(item.version); const enabled = item.status === "available" && (isProviderDiscoveryItem(item) || item.implemented); 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} {isVcsNotReady(item) ? ( Coming Soon ) : null} {authStatus?.badge ? ( {authStatus.label} ) : null}

{itemSummary({ item, auth, authAccount })}

{!isVcsNotReady(item) ? ( ) : null}
); } function SourceControlSectionSkeleton({ title, headerAction, }: { readonly title: string; readonly headerAction?: ReactNode; }) { return ( {SOURCE_CONTROL_SKELETON_ROWS.map((row) => (
))}
); } function EmptySourceControlDiscovery({ error, isPending, onScan, }: { readonly error: string | null; readonly isPending: boolean; readonly onScan: () => void; }) { const hasError = error !== null; return ( {hasError ? "Could not scan the server environment" : "Nothing detected yet"} {hasError ? error : "Install Git on the server, add optional hosting integrations or credentials your workspace needs, then rescan."} ); } 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 = ( } /> Rescan Git and hosting integrations ); return ( {isInitialScanPending ? ( <> ) : hasDiscoveryItems ? ( <> {result.versionControlSystems.length > 0 ? ( {result.versionControlSystems.map((item) => ( ))} ) : null} {result.sourceControlProviders.length > 0 ? ( {result.sourceControlProviders.map((item) => ( ))} ) : null} ) : ( )} ); }