From 0d40ec0841df003f7b9e692fcb03d2541a884b00 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Tue, 14 Apr 2026 12:32:03 -0300 Subject: [PATCH 1/6] feat: add selectedOrganisation Redux slice Introduces a Redux slice to hold the selected organisation ID, replacing the reliance on AccountStore for this state. This is the first step towards migrating organisation selection from Flux to RTK. Closes #7233 Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/common/selectedOrganisationSlice.ts | 24 ++++++++++++++++++++ frontend/common/store.ts | 2 ++ 2 files changed, 26 insertions(+) create mode 100644 frontend/common/selectedOrganisationSlice.ts diff --git a/frontend/common/selectedOrganisationSlice.ts b/frontend/common/selectedOrganisationSlice.ts new file mode 100644 index 000000000000..d400ef754c32 --- /dev/null +++ b/frontend/common/selectedOrganisationSlice.ts @@ -0,0 +1,24 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import API from 'project/api' + +type SelectedOrganisationState = { + id: number | undefined +} + +const initialState: SelectedOrganisationState = { + id: undefined, +} + +const selectedOrganisationSlice = createSlice({ + initialState, + name: 'selectedOrganisation', + reducers: { + setSelectedOrganisationId(state, action: PayloadAction) { + state.id = action.payload + API.setCookie('organisation', `${action.payload}`) + }, + }, +}) + +export const { setSelectedOrganisationId } = selectedOrganisationSlice.actions +export default selectedOrganisationSlice.reducer diff --git a/frontend/common/store.ts b/frontend/common/store.ts index 34c514b3635b..80045546fb5a 100644 --- a/frontend/common/store.ts +++ b/frontend/common/store.ts @@ -13,10 +13,12 @@ import { import storage from 'redux-persist/lib/storage' import { Persistor } from 'redux-persist/es/types' import { service } from './service' +import selectedOrganisationReducer from './selectedOrganisationSlice' // END OF IMPORTS const createStore = () => { const reducer = combineReducers({ [service.reducerPath]: service.reducer, + selectedOrganisation: selectedOrganisationReducer, // END OF REDUCERS }) From b6eeaf0bbf028f0fbd1e87d9578cce790d09433b Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Tue, 14 Apr 2026 12:32:14 -0300 Subject: [PATCH 2/6] feat: add useSelectedOrganisation hook Combines the Redux slice's selected org ID with useGetOrganisationsQuery (list endpoint) to return the full organisation object. Uses the list endpoint which is accessible to all org members, unlike the detail endpoint which returns 403 for non-admins. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../common/hooks/useSelectedOrganisation.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 frontend/common/hooks/useSelectedOrganisation.ts diff --git a/frontend/common/hooks/useSelectedOrganisation.ts b/frontend/common/hooks/useSelectedOrganisation.ts new file mode 100644 index 000000000000..e197f23f225a --- /dev/null +++ b/frontend/common/hooks/useSelectedOrganisation.ts @@ -0,0 +1,17 @@ +import { useSelector } from 'react-redux' +import { useGetOrganisationsQuery } from 'common/services/useOrganisation' +import { StoreStateType } from 'common/store' + +const useSelectedOrganisation = () => { + const selectedId = useSelector( + (state: StoreStateType) => state.selectedOrganisation?.id, + ) + const { data: organisationsData } = useGetOrganisationsQuery({}) + const organisation = organisationsData?.results?.find( + (org) => org.id === selectedId, + ) + + return organisation +} + +export default useSelectedOrganisation From 3bec19819274a9c45d21f87a3b4ba6c4781d8719 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Tue, 14 Apr 2026 12:32:24 -0300 Subject: [PATCH 3/6] refactor: bridge AccountStore org selection to Redux Dispatches setSelectedOrganisationId into the Redux store whenever selectOrganisation or setUser runs in AccountStore. This keeps both stores in sync during the migration from Flux to RTK. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/common/stores/account-store.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/common/stores/account-store.js b/frontend/common/stores/account-store.js index ef32cf654d50..abafc017a9a0 100644 --- a/frontend/common/stores/account-store.js +++ b/frontend/common/stores/account-store.js @@ -12,6 +12,7 @@ import dataRelay from 'data-relay' import { sortBy } from 'lodash' import Project from 'common/project' import { getStore } from 'common/store' +import { setSelectedOrganisationId } from 'common/selectedOrganisationSlice' import { service } from 'common/service' import { getBuildVersion } from 'common/services/useBuildVersion' import { createOnboardingSupportOptIn } from 'common/services/useOnboardingSupportOptIn' @@ -309,6 +310,7 @@ const controller = { selectOrganisation: (id) => { API.setCookie('organisation', `${id}`) store.organisation = find(store.model.organisations, { id }) + getStore().dispatch(setSelectedOrganisationId(id)) store.changed() }, @@ -344,6 +346,9 @@ const controller = { AppActions.getOrganisation(orgId) } } + if (store.organisation?.id) { + getStore().dispatch(setSelectedOrganisationId(store.organisation.id)) + } } AsyncStorage.setItem('user', JSON.stringify(store.model)) From 8ecd0df2d9260d91aa4a4b29bbb5e0d700c2b5ca Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Tue, 14 Apr 2026 12:32:36 -0300 Subject: [PATCH 4/6] fix: org breadcrumb not updating for non-admin users Replace useGetOrganisationQuery (detail endpoint, 403 for non-admins) with useSelectedOrganisation hook that reads from the organisations list endpoint via Redux. This fixes the breadcrumb showing stale org names when non-admin users switch organisations. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/navigation/SelectOrgAndProject.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/frontend/web/components/navigation/SelectOrgAndProject.tsx b/frontend/web/components/navigation/SelectOrgAndProject.tsx index d0a06f5b1763..27110fabe062 100644 --- a/frontend/web/components/navigation/SelectOrgAndProject.tsx +++ b/frontend/web/components/navigation/SelectOrgAndProject.tsx @@ -1,11 +1,10 @@ import React, { FC } from 'react' -import AccountStore from 'common/stores/account-store' import { Link, NavLink } from 'react-router-dom' import BreadcrumbSeparator from 'components/BreadcrumbSeparator' import classNames from 'classnames' import Utils from 'common/utils/utils' import { Project } from 'common/types/responses' -import { useGetOrganisationQuery } from 'common/services/useOrganisation' +import useSelectedOrganisation from 'common/hooks/useSelectedOrganisation' import { appLevelPaths } from './constants' type SelectOrgAndProjectType = { @@ -18,12 +17,7 @@ const SelectOrgAndProject: FC = ({ projectId, }) => { const isAppLevelPage = appLevelPaths.includes(document.location.pathname) - - const organisationId = AccountStore.getOrganisation()?.id - const { data: organisation } = useGetOrganisationQuery( - { id: organisationId as number }, - { skip: !organisationId }, - ) + const organisation = useSelectedOrganisation() return ( @@ -57,9 +51,7 @@ const SelectOrgAndProject: FC = ({ })} to={Utils.getOrganisationHomePage()} > -
- {organisation?.name || AccountStore.getOrganisation()?.name} -
+
{organisation?.name}
From f980efac0134a7a5f3233440c3323ff1321badc2 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Tue, 14 Apr 2026 12:32:45 -0300 Subject: [PATCH 5/6] fix: BreadcrumbSeparator listener cleanup on wrong store The useEffect cleanup was unsubscribing from OrganisationStore instead of AccountStore, leaking event listeners on every unmount/remount. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/web/components/BreadcrumbSeparator.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/web/components/BreadcrumbSeparator.tsx b/frontend/web/components/BreadcrumbSeparator.tsx index 85d4e90ca1ef..1bee94c62a4e 100644 --- a/frontend/web/components/BreadcrumbSeparator.tsx +++ b/frontend/web/components/BreadcrumbSeparator.tsx @@ -13,7 +13,6 @@ import AccountStore from 'common/stores/account-store' import { useGetProjectsQuery } from 'common/services/useProject' import { useGetOrganisationsQuery } from 'common/services/useOrganisation' import { useHistory } from 'react-router-dom' -import OrganisationStore from 'common/stores/organisation-store' import AppActions from 'common/dispatcher/app-actions' import Utils from 'common/utils/utils' import { getStore } from 'common/store' @@ -177,7 +176,7 @@ const BreadcrumbSeparator: FC = ({ } AccountStore.on('change', onChangeAccountStore) return () => { - OrganisationStore.off('change', onChangeAccountStore) + AccountStore.off('change', onChangeAccountStore) } //eslint-disable-next-line }, []) From 2cc922bf52590089230565ee5d0923e38b6d3bc7 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Wed, 15 Apr 2026 09:36:35 -0300 Subject: [PATCH 6/6] fix: Keep selectedOrganisation reducer pure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the cookie side-effect from the reducer: - Reducers must be pure functions; side effects break time-travel debugging, hot reload, and testing - The cookie was being written twice — AccountStore.selectOrganisation already writes it before dispatching Co-Authored-By: Claude Opus 4.6 --- frontend/common/selectedOrganisationSlice.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/common/selectedOrganisationSlice.ts b/frontend/common/selectedOrganisationSlice.ts index d400ef754c32..cffd56636374 100644 --- a/frontend/common/selectedOrganisationSlice.ts +++ b/frontend/common/selectedOrganisationSlice.ts @@ -1,5 +1,4 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import API from 'project/api' type SelectedOrganisationState = { id: number | undefined @@ -15,7 +14,6 @@ const selectedOrganisationSlice = createSlice({ reducers: { setSelectedOrganisationId(state, action: PayloadAction) { state.id = action.payload - API.setCookie('organisation', `${action.payload}`) }, }, })