"use client"; import { useEffect, useRef, useState } from "react"; import Link from "@/components/routing/app-link"; import { Button } from "@kandev/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "@kandev/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { IconBrandGithub, IconBrandGitlab, IconHexagon, IconPlugConnected, IconTicket, } from "@tabler/icons-react"; import { useJiraAvailable } from "@/hooks/domains/jira/use-jira-availability"; import { useLinearAvailable } from "@/hooks/domains/linear/use-linear-availability"; import { useGitHubStatus } from "@/hooks/domains/github/use-github-status"; import { useGitLabAvailable } from "@/hooks/domains/gitlab/use-task-mr"; import type { GitHubStatus } from "@/lib/types/github"; type MobileIntegrationsSectionProps = { onNavigate: () => void; }; type IntegrationId = "github" | "gitlab" | "jira" | "linear"; type IntegrationLink = { id: IntegrationId; label: string; href: string; }; type IntegrationAvailability = { githubReady: boolean; gitlabReady: boolean; jiraAvailable: boolean; linearAvailable: boolean; }; const INTEGRATION_LINKS: IntegrationLink[] = [ { id: "github", label: "GitHub", href: "/github" }, { id: "gitlab", label: "GitLab", href: "/gitlab" }, { id: "jira", label: "Jira", href: "/jira" }, { id: "linear", label: "Linear", href: "/linear" }, ]; const INTEGRATION_ICONS = { github: IconBrandGithub, gitlab: IconBrandGitlab, jira: IconTicket, linear: IconHexagon, } satisfies Record; const HOVER_CLOSE_DELAY_MS = 180; export function getAvailableIntegrationLinks({ githubReady, gitlabReady, jiraAvailable, linearAvailable, }: IntegrationAvailability): IntegrationLink[] { return INTEGRATION_LINKS.filter((link) => { if (link.id === "github") return githubReady; if (link.id === "gitlab") return gitlabReady; if (link.id === "jira") return jiraAvailable; return linearAvailable; }); } function getStatusLabel(loading: boolean | undefined): string { return loading ? "Checking" : "Setup"; } export function getGitHubIntegrationStatus(status: GitHubStatus | null, loading: boolean) { if (status?.authenticated) return { ready: true, label: "Connected" }; if (status?.token_configured) return { ready: true, label: "Configured" }; return { ready: false, label: getStatusLabel(loading) }; } export function useConfiguredIntegrationLinks(): IntegrationLink[] { const { status, loading } = useGitHubStatus(); const gitlabAvailable = useGitLabAvailable(); const jiraAvailable = useJiraAvailable(); const linearAvailable = useLinearAvailable(); const githubStatus = getGitHubIntegrationStatus(status, loading); return getAvailableIntegrationLinks({ githubReady: githubStatus.ready, gitlabReady: gitlabAvailable, jiraAvailable, linearAvailable, }); } export function IntegrationsMenu() { const links = useConfiguredIntegrationLinks(); const [open, setOpen] = useState(false); const closeTimeoutRef = useRef | null>(null); useEffect(() => { return () => { if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); }; }, []); const clearCloseTimeout = () => { if (!closeTimeoutRef.current) return; clearTimeout(closeTimeoutRef.current); closeTimeoutRef.current = null; }; const openOnHover = () => { clearCloseTimeout(); setOpen((current) => (current ? current : true)); }; const closeAfterHover = () => { clearCloseTimeout(); closeTimeoutRef.current = setTimeout(() => setOpen(false), HOVER_CLOSE_DELAY_MS); }; const handleOpenChange = (nextOpen: boolean) => { clearCloseTimeout(); setOpen(nextOpen); }; if (links.length === 0) return null; return ( Integrations {links.map((link) => { const Icon = INTEGRATION_ICONS[link.id]; return ( {link.label} ); })} ); } export function IntegrationsTopbarLinks() { const links = useConfiguredIntegrationLinks(); if (links.length === 0) return null; return ( <> {links.map((link) => { const Icon = INTEGRATION_ICONS[link.id]; return ( {link.label} ); })} ); } export function MobileIntegrationsSection({ onNavigate }: MobileIntegrationsSectionProps) { const links = useConfiguredIntegrationLinks(); if (links.length === 0) return null; return (
Integrations
{links.map((link) => { const Icon = INTEGRATION_ICONS[link.id]; return ( ); })}
); }