Skip to main content
Glama

Convex MCP server

Official
by get-convex
DeploymentDisplay.tsx14.2 kB
import { CommandLineIcon, SignalIcon } from "@heroicons/react/24/outline"; import { CaretSortIcon, GearIcon, GlobeIcon, Pencil2Icon, } from "@radix-ui/react-icons"; import { useCurrentDeployment, useDeployments } from "api/deployments"; import { useCurrentTeam, useTeamEntitlements, useTeamMembers } from "api/teams"; import { useProfile } from "api/profile"; import { useRememberLastViewedDeploymentForProject } from "hooks/useLastViewed"; import { cn } from "@ui/cn"; import { useRouter } from "next/router"; import { DeploymentResponse, ProjectDetails, DeploymentType, } from "generatedApi"; import { Button } from "@ui/Button"; import { ContextMenu } from "@common/features/data/components/ContextMenu"; import { DeploymentMenuOptions } from "components/header/ProjectSelector/DeploymentMenuOptions"; import { useCurrentProject } from "api/projects"; import { useRef, useState, useEffect } from "react"; import { PROVISION_PROD_PAGE_NAME } from "@common/lib/deploymentContext"; import { useHotkeys } from "react-hotkeys-hook"; import { useListVanityDomains } from "api/vanityDomains"; import { useQuery } from "convex/react"; import udfs from "@common/udfs"; import { ProdProvider } from "components/projectSettings/CustomDomains"; import { useContainerWidth } from "../hooks/useContainerWidth"; // New component for prod domain info function ProdDomainInfo({ deployment, deployments, whoseName, }: { deployment: DeploymentResponse; deployments: DeploymentResponse[]; whoseName: string | null; }) { const team = useCurrentTeam(); const hasEntitlement = !!useTeamEntitlements(team?.id)?.customDomainsEnabled; const domains = useListVanityDomains( hasEntitlement && deployment?.deploymentType === "prod" ? deployment?.name : undefined, ); const vanityCloudDomains = domains?.filter( (d) => d.requestDestination === "convexCloud", ); const canonicalCloudUrl = useQuery(udfs.convexCloudUrl.default); const vanityUrl = vanityCloudDomains?.find((d) => d.domain === canonicalCloudUrl)?.domain || vanityCloudDomains?.[0]?.domain; return ( <DeploymentLabel deployment={deployment} whoseName={whoseName} deployments={deployments} vanityUrl={vanityUrl} /> ); } // New wrapper to keep the deployment label node stable function DeploymentLabelWrapper({ deployment, whoseName, deployments, prod, }: { deployment: DeploymentResponse; whoseName: string | null; deployments: DeploymentResponse[]; prod?: DeploymentResponse; }) { if (prod && deployment.name === prod.name) { return ( <ProdProvider deploymentName={prod.name}> <ProdDomainInfo deployment={deployment} whoseName={whoseName} deployments={deployments} /> </ProdProvider> ); } return ( <DeploymentLabel deployment={deployment} whoseName={whoseName} deployments={deployments} /> ); } export function DeploymentDisplay({ project }: { project: ProjectDetails }) { const router = useRouter(); const deployment = useCurrentDeployment(); const member = useProfile(); useRememberLastViewedDeploymentForProject(project.slug, deployment?.name); const teamMembers = useTeamMembers(project.teamId); const isProdSelected = deployment?.deploymentType === "prod"; const isPreview = deployment?.deploymentType === "preview"; const isDoneLoading = isProdSelected || // prod deploys are straightforward isPreview || // preview deployments don't require additional info (deployment !== undefined && member !== undefined && teamMembers !== undefined); // info required to render dev deploy const whose = teamMembers?.find((tm) => tm.id === deployment?.creator); const profile = useProfile(); const whoseName = whose?.email === profile?.email ? null : whose?.name || whose?.email || "Teammate"; const isProjectSettings = router.route.endsWith("/[project]/settings"); const team = useCurrentTeam(); const currentProject = useCurrentProject(); const { deployments: deploymentData } = useDeployments(currentProject?.id); const deployments = deploymentData || []; const projectSlug = currentProject?.slug; const selectedTeamSlug = team?.slug; const projectsURI = `/t/${selectedTeamSlug}/${projectSlug}`; const currentView = router.asPath.split("/").slice(5).join("/"); const devDeployments = deployments.filter( (d) => d.deploymentType === "dev" && d.creator === member?.id, ); const prod = deployments.find((d) => d.deploymentType === "prod"); // Hotkeys useHotkeys( "ctrl+alt+1", () => { if (prod) { void router.push(`${projectsURI}/${prod.name}/${currentView}`); } else { void router.push(`${projectsURI}/${PROVISION_PROD_PAGE_NAME}`); } }, [prod, projectsURI, currentView], ); useHotkeys( Array.from({ length: devDeployments.length }, (_, idx) => [ `ctrl+alt+${idx + 2}`, ]).flat(), (event, handler) => { const keyStr = handler.keys?.[0] || ""; if (keyStr) { const devIdx = parseInt(keyStr.split("+").pop() || "", 10) - 2; if (devIdx >= 0 && devIdx < devDeployments.length) { void router.push( `${projectsURI}/${devDeployments[devIdx].name}/${currentView}`, ); } } }, [devDeployments, projectsURI, currentView], ); useHotkeys( "ctrl+alt+s", () => { void router.push(`${projectsURI}/settings`); }, [projectsURI], ); // ContextMenu trigger state const buttonRef = useRef<HTMLButtonElement>(null); const [menuTarget, setMenuTarget] = useState<{ x: number; y: number } | null>( null, ); const openMenu = (e: React.MouseEvent) => { e.preventDefault(); if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); setMenuTarget({ x: rect.left, y: rect.bottom }); } }; const closeMenu = () => setMenuTarget(null); return isProjectSettings ? ( team && currentProject ? ( <div key="projectSettings" className="my-2 mr-px flex grow items-stretch overflow-visible rounded-full bg-background-secondary" > <Button variant="unstyled" className={cn( "flex h-full items-center gap-2 rounded-full px-3", "border bg-background-secondary text-content-primary", "truncate text-sm font-medium transition-opacity hover:bg-background-tertiary", menuTarget && "border-border-selected bg-background-tertiary", )} ref={buttonRef} tabIndex={0} role="button" aria-haspopup="menu" aria-expanded={!!menuTarget} onClick={openMenu} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") openMenu(e as any); }} > <GearIcon className="size-4 min-w-4" /> <span className="max-w-24 truncate sm:contents"> Project settings </span> <CaretSortIcon className="ml-auto size-5 bg-transparent" /> <ContextMenu target={menuTarget} onClose={closeMenu}> <DeploymentMenuOptions team={team} project={currentProject} deployments={deployments} /> </ContextMenu> </Button> </div> ) : null ) : !isDoneLoading ? null : ( <DeploymentLabelWrapper deployment={deployment} whoseName={whoseName} deployments={deployments} prod={prod} /> ); } export function DeploymentLabel({ whoseName, deployment, deployments, vanityUrl, }: { deployment: DeploymentResponse; deployments: DeploymentResponse[]; whoseName: string | null; vanityUrl?: string; }) { const team = useCurrentTeam(); const project = useCurrentProject(); const buttonRef = useRef<HTMLButtonElement>(null); const [menuTarget, setMenuTarget] = useState<{ x: number; y: number } | null>( null, ); const openMenu = (e: React.MouseEvent) => { e.preventDefault(); if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); setMenuTarget({ x: rect.left, y: rect.bottom }); } }; const closeMenu = () => setMenuTarget(null); const [containerRef, containerWidth] = useContainerWidth<HTMLDivElement>(); // Hysteresis buffer for domain display const DOMAIN_SHOW_THRESHOLD = 350; const DOMAIN_HIDE_THRESHOLD = 320; const [showDomain, setShowDomain] = useState( containerWidth > DOMAIN_SHOW_THRESHOLD, ); const prevShowDomain = useRef(showDomain); useEffect(() => { if (showDomain && containerWidth < DOMAIN_HIDE_THRESHOLD) { setShowDomain(false); } if (!showDomain && containerWidth > DOMAIN_SHOW_THRESHOLD) { setShowDomain(true); } prevShowDomain.current = showDomain; }, [containerWidth, showDomain]); // Estimate minimum widths (adjust as needed) const minTypeWidth = 100; const minDomainWidth = 180; const minNameWidth = 120; const padding = 48; // for icons, gaps, caret, etc. // Decide what to show const showType = true; const showName = containerWidth > minTypeWidth + minDomainWidth + minNameWidth + padding; if (!team || !project) { return null; } return ( <div ref={containerRef} className={cn( "my-2 flex min-w-24 grow overflow-visible p-px", "overflow-auto", )} > <Button variant="unstyled" id="select-deployment" className={cn( "flex h-[2.3125rem] items-center gap-2 truncate rounded-full border text-sm font-medium transition-opacity hover:opacity-80", menuTarget && "opacity-80", "focus-visible:ring-1 focus-visible:ring-border-selected focus-visible:outline-hidden", getBackgroundColor(deployment.deploymentType), )} type="button" ref={buttonRef} aria-haspopup="menu" aria-expanded={!!menuTarget} tabIndex={0} onClick={openMenu} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") openMenu(e as any); }} > <div className="flex h-full w-full animate-fadeInFromLoading cursor-pointer items-center gap-1 px-4"> {showType && ( <> {deployment.deploymentType === "dev" ? ( deployment.kind === "local" ? ( <CommandLineIcon className="size-4 min-w-4" /> ) : ( <GlobeIcon className="size-4 min-w-4" /> ) ) : deployment.deploymentType === "prod" ? ( <SignalIcon className="size-4 min-w-4" /> ) : deployment.deploymentType === "preview" ? ( <Pencil2Icon className="size-4 min-w-4" /> ) : null} <span className="max-w-24 truncate sm:contents"> {getDeploymentLabel({ deployment, whoseName, })} </span> </> )} {showDomain && deployment.kind === "cloud" && vanityUrl && ( <> <span className="animate-fadeInFromLoading px-0.5 font-normal" role="separator" > • </span> <span className="block max-w-60 animate-fadeInFromLoading truncate font-mono font-normal" title={vanityUrl} > {vanityUrl} </span> </> )} {showName && deployment.kind === "cloud" && ( <> <span className="animate-fadeInFromLoading px-0.5 font-normal" role="separator" > • </span> <span className="animate-fadeInFromLoading font-mono font-normal"> {deployment.name} </span> </> )} {showName && deployment.kind === "local" && ( <> <span className="animate-fadeInFromLoading px-0.5 font-normal" role="separator" > • </span> <span className="animate-fadeInFromLoading font-mono text-sm font-normal"> Port {deployment.port} </span> </> )} <CaretSortIcon className={cn( "ml-auto size-5 shrink-0", getBackgroundColor(deployment.deploymentType), "bg-transparent", )} /> <ContextMenu target={menuTarget} onClose={closeMenu}> <DeploymentMenuOptions team={team} project={project} deployments={deployments} /> </ContextMenu> </div> </Button> </div> ); } export function getBackgroundColor(deploymentType: DeploymentType): string { switch (deploymentType) { case "prod": return "border-purple-600 dark:border-purple-100 bg-purple-100 text-purple-600 dark:bg-purple-700 dark:text-purple-100"; case "preview": return "border-orange-600 dark:border-orange-400 bg-orange-100 text-orange-600 dark:bg-orange-900 dark:text-orange-400"; case "dev": return "border-green-600 dark:border-green-400 bg-green-100 text-green-600 dark:bg-green-900 dark:text-green-400"; default: { deploymentType satisfies never; return ""; } } } export function getDeploymentLabel({ deployment, whoseName, }: { deployment: DeploymentResponse; whoseName: string | null; // null = mine }): string { switch (deployment.deploymentType) { case "prod": return "Production"; case "preview": return `Preview: ${deployment.previewIdentifier || "Unknown"}`; case "dev": { if (deployment.kind === "local") { return deployment.deviceName; } return whoseName === null ? "Development (Cloud)" : `${whoseName}’s Dev`; } default: { deployment.deploymentType satisfies never; return ""; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server