diff --git a/src/components/server-catalog/CatalogResults.tsx b/src/components/server-catalog/CatalogResults.tsx index 7d47fdb2..6bde098d 100644 --- a/src/components/server-catalog/CatalogResults.tsx +++ b/src/components/server-catalog/CatalogResults.tsx @@ -23,6 +23,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { StatusIndicator } from "@/components/ui/status-indicator"; import type { CatalogServer } from "@/generated/types"; import { useDebouncedValue } from "@/hooks/useDebouncedValue"; import { getAuthTypeGroupId, getAuthTypeGroupLabelId } from "@/utils/catalogAuthTypes"; @@ -30,6 +31,7 @@ import { getTagLabels } from "@/utils/tags"; const EMPTY_PENDING_IDS: ReadonlySet = new Set(); const EMPTY_OAUTH_STATUSES: Readonly> = {}; +const EMPTY_ADD_ERRORS: Readonly> = {}; function getOAuthCardState(server: CatalogServer, status?: OAuthGatewayStatus) { const tokenState = status?.user_token_status?.status; @@ -77,6 +79,8 @@ function CatalogCard({ canTest, canDisconnect, oauthStatuses, + addError, + onAddErrorRead, }: { server: CatalogServer; onView: (trigger: HTMLElement) => void; @@ -90,6 +94,10 @@ function CatalogCard({ canTest: boolean; canDisconnect: boolean; oauthStatuses?: Readonly>; + /** Why the last add attempt failed, resolved to the server's reason or the fallback. */ + addError?: string; + /** Called once the reason has been read, which is what retires the error. */ + onAddErrorRead?: () => void; }) { const intl = useIntl(); const headingId = useId(); @@ -97,6 +105,7 @@ function CatalogCard({ const actionsTriggerRef = useRef(null); const pendingDetailsTriggerRef = useRef(null); const shouldTransferAddFocusRef = useRef(false); + const shouldRestoreErrorFocusRef = useRef(false); const oauthState = getOAuthCardState( server, server.gateway_id ? oauthStatuses?.[server.gateway_id] : undefined, @@ -117,6 +126,21 @@ function CatalogCard({ } }, [isAdding, server.is_registered]); + // Retiring the error unmounts the trigger the popover returns focus to. + useEffect(() => { + if (addError || !shouldRestoreErrorFocusRef.current) return; + shouldRestoreErrorFocusRef.current = false; + + const timeoutId = window.setTimeout(() => { + const trigger = addTriggerRef.current; + if (!trigger) return; + const { activeElement, body } = trigger.ownerDocument; + if (activeElement === null || activeElement === body) trigger.focus(); + }, 0); + + return () => window.clearTimeout(timeoutId); + }, [addError]); + return (
  • @@ -278,6 +302,32 @@ function CatalogCard({