diff --git a/ui/src/components/SidebarAccountMenu.test.tsx b/ui/src/components/SidebarAccountMenu.test.tsx index 5935f1a6ef..f596c625d1 100644 --- a/ui/src/components/SidebarAccountMenu.test.tsx +++ b/ui/src/components/SidebarAccountMenu.test.tsx @@ -100,7 +100,7 @@ describe("SidebarAccountMenu", () => { vi.clearAllMocks(); }); - it("renders the signed-in user and opens the account card menu", async () => { + it("keeps authenticated self-hosted sign-out on the local auth flow", async () => { const root = createRoot(container); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, @@ -167,19 +167,29 @@ describe("SidebarAccountMenu", () => { await flushReact(); expect(mockAuthApi.signOut).toHaveBeenCalledOnce(); - expect(mockNavigateTopLevel).toHaveBeenCalledWith("/cloud/logout"); + expect(mockNavigateTopLevel).not.toHaveBeenCalled(); + expect(queryClient.getQueryState(queryKeys.health)?.isInvalidated).toBe(true); await act(async () => { root.unmount(); }); }); - it("falls back to the managed logout route when sign-out omits a redirect", async () => { - mockAuthApi.signOut.mockResolvedValue({ success: true }); + it("navigates cloud-managed sign-out through the harness without calling local auth", async () => { const root = createRoot(container); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); + queryClient.setQueryData(queryKeys.health, { + status: "ok", + deploymentMode: "authenticated", + cloud: { + managed: true, + managedBy: "paperclip-cloud", + stackSlug: "acme-labs", + cloudBaseUrl: "https://cloud.example.test", + }, + }); await act(async () => { root.render( @@ -198,6 +208,7 @@ describe("SidebarAccountMenu", () => { }); await flushReact(); + expect(mockAuthApi.signOut).not.toHaveBeenCalled(); expect(mockNavigateTopLevel).toHaveBeenCalledWith("/cloud/logout"); await act(async () => { diff --git a/ui/src/components/SidebarAccountMenu.tsx b/ui/src/components/SidebarAccountMenu.tsx index 368888bf1e..8713b12de1 100644 --- a/ui/src/components/SidebarAccountMenu.tsx +++ b/ui/src/components/SidebarAccountMenu.tsx @@ -13,6 +13,7 @@ import { Link } from "@/lib/router"; import { authApi } from "@/api/auth"; import { queryKeys } from "@/lib/queryKeys"; import { navigateTopLevel } from "@/lib/browserNavigation"; +import { useCloudInstance } from "@/hooks/useCloudInstance"; import { useSidebar } from "../context/SidebarContext"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -120,6 +121,7 @@ export function SidebarAccountMenu({ }: SidebarAccountMenuProps) { const [internalOpen, setInternalOpen] = useState(false); const queryClient = useQueryClient(); + const isCloud = Boolean(useCloudInstance()); const { isMobile, setSidebarOpen, collapsed, peeking } = useSidebar(); const rail = collapsed && !peeking; const open = controlledOpen ?? internalOpen; @@ -132,12 +134,8 @@ export function SidebarAccountMenu({ const signOutMutation = useMutation({ mutationFn: () => authApi.signOut(), - onSuccess: async (result) => { + onSuccess: async () => { setOpen(false); - if (deploymentMode === "authenticated") { - navigateTopLevel(result?.redirectTo?.trim() || MANAGED_SIGN_OUT_PATH); - return; - } await queryClient.invalidateQueries({ queryKey: queryKeys.auth.session }); await queryClient.invalidateQueries({ queryKey: queryKeys.health }); }, @@ -161,6 +159,15 @@ export function SidebarAccountMenu({ if (isMobile) setSidebarOpen(false); } + function handleSignOut() { + if (isCloud) { + closeNavigationChrome(); + navigateTopLevel(MANAGED_SIGN_OUT_PATH); + return; + } + signOutMutation.mutate(); + } + return (