From 61f19667b64f195ecdc7f066b9dcb7fb0e914a31 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Wed, 19 Aug 2026 10:07:23 -0700 Subject: [PATCH] fix(settings): drop the settings return url when the workspace changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settings Back button restores a return url captured on entry, but a workspace switch made from inside settings keeps the user in the new workspace without touching that stored path — so Back pushed them back into the workspace they had left, while the sidebar still read as the new one. Discard a stored return url that names a different workspace and fall back to the current workspace root. --- apps/sim/hooks/use-oauth-return.ts | 2 +- .../sim/hooks/use-settings-navigation.test.ts | 42 +++++++++++++++- apps/sim/hooks/use-settings-navigation.ts | 48 +++++++++++++++---- 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/apps/sim/hooks/use-oauth-return.ts b/apps/sim/hooks/use-oauth-return.ts index cd595b345b9..a79f4a8297c 100644 --- a/apps/sim/hooks/use-oauth-return.ts +++ b/apps/sim/hooks/use-oauth-return.ts @@ -26,9 +26,9 @@ import { getDesktopBridge } from '@/lib/desktop' import { oauthConnectionsKeys } from '@/hooks/queries/oauth/oauth-connections' import { workspaceCredentialKeys } from '@/hooks/queries/utils/credential-keys' import { requireWorkspaceCredentialListResponse } from '@/hooks/queries/utils/fetch-workspace-credentials' +import { SETTINGS_RETURN_URL_KEY } from '@/hooks/use-settings-navigation' const OAUTH_CREDENTIAL_UPDATED_EVENT = 'oauth-credentials-updated' -const SETTINGS_RETURN_URL_KEY = 'settings-return-url' const CONTEXT_MAX_AGE_MS = 15 * 60 * 1000 export interface OAuthResultMessage { diff --git a/apps/sim/hooks/use-settings-navigation.test.ts b/apps/sim/hooks/use-settings-navigation.test.ts index e2839fe0a22..2d10a0a98a3 100644 --- a/apps/sim/hooks/use-settings-navigation.test.ts +++ b/apps/sim/hooks/use-settings-navigation.test.ts @@ -15,7 +15,7 @@ vi.mock('@/lib/auth/auth-client', () => ({ useSession: vi.fn(() => ({ data: null, isPending: false })), })) -import { resolveSettingsHref } from '@/hooks/use-settings-navigation' +import { resolveSettingsHref, resolveSettingsReturnUrl } from '@/hooks/use-settings-navigation' const HOST_CONTEXT: WorkspaceHostContext = { workspace: { @@ -107,3 +107,43 @@ describe('resolveSettingsHref unified settings navigation', () => { ).toBe('/workspace/workspace-b/settings/billing') }) }) + +describe('resolveSettingsReturnUrl', () => { + const fallback = '/workspace/workspace-b' + + it('returns the stored url when it belongs to the current workspace', () => { + expect( + resolveSettingsReturnUrl({ + storedUrl: '/workspace/workspace-b/w/workflow-a', + workspaceId: 'workspace-b', + fallback, + }) + ).toBe('/workspace/workspace-b/w/workflow-a') + }) + + it('discards a stored url captured in a workspace the user has since left', () => { + expect( + resolveSettingsReturnUrl({ + storedUrl: '/workspace/workspace-a/w/workflow-a', + workspaceId: 'workspace-b', + fallback, + }) + ).toBe(fallback) + }) + + it('keeps workspace-agnostic stored urls', () => { + expect( + resolveSettingsReturnUrl({ + storedUrl: '/account/settings/billing', + workspaceId: 'workspace-b', + fallback, + }) + ).toBe('/account/settings/billing') + }) + + it('falls back when nothing was stored', () => { + expect( + resolveSettingsReturnUrl({ storedUrl: null, workspaceId: 'workspace-b', fallback }) + ).toBe(fallback) + }) +}) diff --git a/apps/sim/hooks/use-settings-navigation.ts b/apps/sim/hooks/use-settings-navigation.ts index 532ffb84bc7..4b2d0635510 100644 --- a/apps/sim/hooks/use-settings-navigation.ts +++ b/apps/sim/hooks/use-settings-navigation.ts @@ -8,7 +8,7 @@ import { canManageWorkspaceBilling } from '@/lib/billing/workspace-permissions' import { useOptionalWorkspaceHostContext } from '@/app/workspace/[workspaceId]/providers/workspace-host-provider' import type { SettingsSection } from '@/app/workspace/[workspaceId]/settings/navigation' -const SETTINGS_RETURN_URL_KEY = 'settings-return-url' +export const SETTINGS_RETURN_URL_KEY = 'settings-return-url' interface SettingsNavigationOptions { section?: SettingsSection @@ -57,6 +57,31 @@ export function resolveSettingsHref({ return query ? `${pathname}?${query}` : pathname } +interface ResolveSettingsReturnUrlParams { + storedUrl: string | null + workspaceId?: string + fallback: string +} + +/** + * Resolves the stored settings return url, discarding it when it points at a + * different workspace than the one currently open. Switching workspaces from + * settings keeps the user on the new workspace, so a return url captured in the + * old one would silently navigate them back out of it. + */ +export function resolveSettingsReturnUrl({ + storedUrl, + workspaceId, + fallback, +}: ResolveSettingsReturnUrlParams): string { + if (!storedUrl) return fallback + const [, root, storedWorkspaceId] = storedUrl.split('/') + if (root === 'workspace' && storedWorkspaceId && storedWorkspaceId !== workspaceId) { + return fallback + } + return storedUrl +} + export function useSettingsNavigation(): UseSettingsNavigationReturn { const router = useRouter() const params = useParams<{ workspaceId?: string }>() @@ -77,15 +102,18 @@ export function useSettingsNavigation(): UseSettingsNavigationReturn { [hostContext, session?.user?.id, workspaceId] ) - const popSettingsReturnUrl = useCallback((fallback: string): string => { - try { - const url = sessionStorage.getItem(SETTINGS_RETURN_URL_KEY) - sessionStorage.removeItem(SETTINGS_RETURN_URL_KEY) - return url ?? fallback - } catch { - return fallback - } - }, []) + const popSettingsReturnUrl = useCallback( + (fallback: string): string => { + try { + const storedUrl = sessionStorage.getItem(SETTINGS_RETURN_URL_KEY) + sessionStorage.removeItem(SETTINGS_RETURN_URL_KEY) + return resolveSettingsReturnUrl({ storedUrl, workspaceId, fallback }) + } catch { + return fallback + } + }, + [workspaceId] + ) const navigateToSettings = useCallback( (options?: SettingsNavigationOptions) => {