fix(frontend): logout console issues (#11400)

## Changes 🏗️

Fixed the logout errors by removing duplicate redirects. `serverLogout`
was calling `redirect("/login")` (which throws `NEXT_REDIRECT`), and
then `useSupabaseStore` was also calling `router.refresh()`, causing
conflicts.

Updated `serverLogout` to return a result object instead of redirecting,
and moved the redirect to the client using `router.push("/login")` after
logout completes. This removes the `NEXT_REDIRECT` error and ensures a
single redirect.

<img width="800" height="706" alt="Screenshot 2025-11-18 at 16 14 54"
src="https://github.com/user-attachments/assets/38e0e55c-f48d-4b25-a07b-d4729e229c70"
/>

Also addressed 401 errors during logout. Hooks like `useCredits` were
still making API calls after logout, causing "Authorization header is
missing" errors. Added a check in `_makeClientRequest` to detect
logout-in-progress and suppress authentication errors during that
window. This prevents console noise and avoids unnecessary error
handling.

<img width="800" height="742" alt="Screenshot 2025-11-18 at 16 14 45"
src="https://github.com/user-attachments/assets/6fb2270a-97a0-4411-9e5a-9b4b52117af3"
/>


## Checklist 📋

### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Log out of your account
  - [x] There are no errors showing up on the browser devtools
This commit is contained in:
Ubbe
2025-11-18 16:41:51 +07:00
committed by GitHub
parent 34c9ecf6bc
commit 3b34c04a7a
6 changed files with 50 additions and 39 deletions

View File

@@ -1,15 +1,13 @@
"use client";
import { IconLogOut } from "@/components/__legacy__/ui/icons";
import { LoadingSpinner } from "@/components/__legacy__/ui/loading";
import { toast } from "@/components/molecules/Toast/use-toast";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { cn } from "@/lib/utils";
import * as Sentry from "@sentry/nextjs";
import { useRouter } from "next/navigation";
import { useTransition } from "react";
import { toast } from "@/components/molecules/Toast/use-toast";
export function AccountLogoutOption() {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const supabase = useSupabase();
@@ -17,7 +15,6 @@ export function AccountLogoutOption() {
startTransition(async () => {
try {
await supabase.logOut();
router.refresh();
} catch (e) {
Sentry.captureException(e);
toast({

View File

@@ -1,10 +1,11 @@
"use client";
import { useMemo } from "react";
import { useGetV2GetUserProfile } from "@/app/api/__generated__/endpoints/store/store";
import { IconAutoGPTLogo, IconType } from "@/components/__legacy__/ui/icons";
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { useMemo } from "react";
import { getAccountMenuItems, loggedInLinks, loggedOutLinks } from "../helpers";
import { AccountMenu } from "./AccountMenu/AccountMenu";
import { AgentActivityDropdown } from "./AgentActivityDropdown/AgentActivityDropdown";
@@ -12,7 +13,6 @@ import { LoginButton } from "./LoginButton";
import { MobileNavBar } from "./MobileNavbar/MobileNavBar";
import { NavbarLink } from "./NavbarLink";
import { Wallet } from "./Wallet/Wallet";
import { useGetFlag, Flag } from "@/services/feature-flags/use-get-flag";
interface NavbarViewProps {
isLoggedIn: boolean;
}
@@ -41,7 +41,7 @@ export const NavbarView = ({ isLoggedIn }: NavbarViewProps) => {
<nav className="sticky top-0 z-40 inline-flex h-[60px] w-full items-center border border-white/50 bg-[#f3f4f6]/20 p-3 backdrop-blur-[26px]">
{/* Left section */}
{!isSmallScreen ? (
<div className="flex flex-1 items-center gap-3 gap-5">
<div className="flex flex-1 items-center gap-5">
{isLoggedIn
? linksWithChat.map((link) => (
<NavbarLink

View File

@@ -1,11 +1,12 @@
import { getWebSocketToken } from "@/lib/supabase/actions";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { createBrowserClient } from "@supabase/ssr";
import type { SupabaseClient } from "@supabase/supabase-js";
import { Key, storage } from "@/services/storage/local-storage";
import { IMPERSONATION_HEADER_NAME } from "@/lib/constants";
import { ImpersonationState } from "@/lib/impersonation";
import { getWebSocketToken } from "@/lib/supabase/actions";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { environment } from "@/services/environment";
import { Key, storage } from "@/services/storage/local-storage";
import * as Sentry from "@sentry/nextjs";
import { createBrowserClient } from "@supabase/ssr";
import type { SupabaseClient } from "@supabase/supabase-js";
import type {
AddUserCreditsResponse,
AnalyticsDetails,
@@ -70,7 +71,6 @@ import type {
UsersBalanceHistoryResponse,
WebSocketNotification,
} from "./types";
import { environment } from "@/services/environment";
const isClient = environment.isClientSide();
@@ -1006,8 +1006,13 @@ export default class BackendAPI {
// Dynamic import is required even for client-only functions because helpers.ts
// has server-only imports (like getServerSupabase) at the top level. Static imports
// would bundle server-only code into the client bundle, causing runtime errors.
const { buildClientUrl, buildUrlWithQuery, handleFetchError } =
await import("./helpers");
const {
buildClientUrl,
buildUrlWithQuery,
handleFetchError,
isLogoutInProgress,
isAuthenticationError,
} = await import("./helpers");
const payloadAsQuery = ["GET", "DELETE"].includes(method);
let url = buildClientUrl(path);
@@ -1034,7 +1039,18 @@ export default class BackendAPI {
});
if (!response.ok) {
throw await handleFetchError(response);
const error = await handleFetchError(response);
if (
isAuthenticationError(response, error.message) &&
isLogoutInProgress()
) {
console.debug(
"Authentication request failed during logout, ignoring:",
error.message,
);
return null;
}
throw error;
}
return await response.json();

View File

@@ -1,7 +1,7 @@
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { Key, storage } from "@/services/storage/local-storage";
import { environment } from "@/services/environment";
import { IMPERSONATION_HEADER_NAME } from "@/lib/constants";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { environment } from "@/services/environment";
import { Key, storage } from "@/services/storage/local-storage";
import { GraphValidationErrorResponse } from "./types";
@@ -221,7 +221,7 @@ export async function parseApiResponse(response: Response): Promise<any> {
}
}
function isAuthenticationError(
export function isAuthenticationError(
response: Response,
errorDetail: string,
): boolean {
@@ -234,7 +234,7 @@ function isAuthenticationError(
);
}
function isLogoutInProgress(): boolean {
export function isLogoutInProgress(): boolean {
if (environment.isServerSide()) return false;
try {

View File

@@ -2,7 +2,6 @@
import * as Sentry from "@sentry/nextjs";
import type { User } from "@supabase/supabase-js";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { getRedirectPath } from "./helpers";
import { getServerSupabase } from "./server/getServerSupabase";
@@ -157,8 +156,7 @@ export async function serverLogout(options: ServerLogoutOptions = {}) {
const supabase = await getServerSupabase();
if (!supabase) {
redirect("/login");
return;
return { success: true };
}
try {
@@ -170,14 +168,18 @@ export async function serverLogout(options: ServerLogoutOptions = {}) {
if (error) {
console.error("Error logging out:", error);
return { success: false, error: error.message };
}
} catch (error) {
console.error("Logout error:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
};
}
// Clear all cached data and redirect
revalidatePath("/", "layout");
redirect("/login");
return { success: true };
},
);
}

View File

@@ -122,20 +122,16 @@ export const useSupabaseStore = create<SupabaseStoreState>((set, get) => {
broadcastLogout();
try {
await serverLogout(options);
} catch (error) {
console.error("Error logging out:", error);
} finally {
set({
user: null,
hasLoadedUser: false,
isUserLoading: false,
});
set({
user: null,
hasLoadedUser: false,
isUserLoading: false,
});
if (router) {
router.refresh();
}
const result = await serverLogout(options);
if (result.success && router) {
router.push("/login");
}
}