Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/hooks/useIsWorkspacesTabFocused.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WojtekBoman can you add unit test for this please?

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import getActiveTabName from '@libs/Navigation/helpers/getActiveTabName';
import type {NavigationRoute} from '@libs/Navigation/types';
import NAVIGATORS from '@src/NAVIGATORS';
import useRootNavigationState from './useRootNavigationState';

/**
* Returns true when the Workspaces tab is the active tab in the top-most TAB_NAVIGATOR.
* Stays true when an RHP is pushed on top of a workspace screen, unlike `useIsFocused()`
* which becomes false because the RHP is the leaf focused route.
*/
function useIsWorkspacesTabFocused(): boolean {
return useRootNavigationState((state) => {
if (!state) {
return false;
}
const topTabNavigator = state.routes.findLast((route) => route.name === NAVIGATORS.TAB_NAVIGATOR) as NavigationRoute | undefined;
return getActiveTabName(topTabNavigator) === NAVIGATORS.WORKSPACE_NAVIGATOR;
});
}

export default useIsWorkspacesTabFocused;
7 changes: 6 additions & 1 deletion src/pages/workspace/AccessOrNotFoundWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPageNotFoundView';
import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useIsWorkspacesTabFocused from '@hooks/useIsWorkspacesTabFocused';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePreferredPolicy from '@hooks/usePreferredPolicy';
Expand Down Expand Up @@ -152,6 +153,7 @@ function AccessOrNotFoundWrapper({
const isFromGlobalCreate = !!reportID && isEmptyObject(report?.reportID);
const pendingField = featureName ? policy?.pendingFields?.[featureName] : undefined;
const isFocused = useIsFocused();
const isWorkspacesTabFocused = useIsWorkspacesTabFocused();

useEffect(() => {
if (!isPolicyIDInRoute || !isEmptyObject(policy)) {
Expand Down Expand Up @@ -180,7 +182,10 @@ function AccessOrNotFoundWrapper({
}, true);

const isPolicyNotAccessible = !isPolicyAccessible(policy, login);
const shouldShowNotFoundPage = isFocused && ((!isMoneyRequest && !isFromGlobalCreate && isPolicyNotAccessible) || !isPageAccessible || shouldBeBlocked);
// Gate on `isFocused || isWorkspacesTabFocused` so the fallback renders for both
// - non-workspace consumers of this wrapper (IOU create routes, etc.) where `isFocused` is the meaningful signal, and
// - workspace central-pane usages where an RHP overlay makes `isFocused` false but the Workspaces tab is still active.
const shouldShowNotFoundPage = (isFocused || isWorkspacesTabFocused) && ((!isMoneyRequest && !isFromGlobalCreate && isPolicyNotAccessible) || !isPageAccessible || shouldBeBlocked);
// We only update the feature state if it isn't pending.
// This is because the feature state changes several times during the creation of a workspace, while we are waiting for a response from the backend.
// Without this, we can be unexpectedly navigated to the More Features page.
Expand Down
4 changes: 3 additions & 1 deletion src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useCardFeedErrors from '@hooks/useCardFeedErrors';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
import useGetReceiptPartnersIntegrationData from '@hooks/useGetReceiptPartnersIntegrationData';
import useIsWorkspacesTabFocused from '@hooks/useIsWorkspacesTabFocused';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
Expand Down Expand Up @@ -97,6 +98,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
const {isBetaEnabled} = usePermissions();

const isFocused = useIsFocused();
const isWorkspacesTabFocused = useIsWorkspacesTabFocused();
const activeRoute = useNavigationState((state) => findFocusedRoute(state)?.name);
const waitForNavigate = useWaitForNavigation();
const {singleExecution, isExecuting} = useSingleExecution();
Expand Down Expand Up @@ -190,7 +192,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
const prevIsPendingDelete = isPendingDeletePolicy(prevPolicy);
// While the policy is being fetched (e.g., right after joinAccessiblePolicy), the role is not yet populated,
// so checkIfShouldShowPolicy returns false. Suppress NotFound during this loading window.
const shouldShowNotFoundPage = isFocused && !shouldShowPolicy && !policy?.isLoading && (!isPendingDelete || prevIsPendingDelete);
const shouldShowNotFoundPage = isWorkspacesTabFocused && !shouldShowPolicy && !policy?.isLoading && (!isPendingDelete || prevIsPendingDelete);
const fetchPolicyData = () => {
if (policyDraft?.id || !isFocused) {
return;
Expand Down
11 changes: 7 additions & 4 deletions src/pages/workspace/WorkspacePageWithSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/typ
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollViewWithContext from '@components/ScrollViewWithContext';
import useAndroidBackButtonHandler from '@hooks/useAndroidBackButtonHandler';
import useIsWorkspacesTabFocused from '@hooks/useIsWorkspacesTabFocused';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePrevious from '@hooks/usePrevious';
Expand Down Expand Up @@ -151,6 +152,7 @@ function WorkspacePageWithSections({
const {shouldUseNarrowLayout} = useResponsiveLayout();
const firstRender = useRef(showLoadingAsFirstRender);
const isFocused = useIsFocused();
const isWorkspacesTabFocused = useIsWorkspacesTabFocused();
const prevPolicy = usePrevious(policy);

useEffect(() => {
Expand All @@ -167,9 +169,10 @@ function WorkspacePageWithSections({
const prevIsPendingDelete = isPendingDeletePolicy(prevPolicy);

const shouldShow = useMemo(() => {
// Don't trigger the not-found view while the screen is in the background — prevents unexpected
// navigation when the workspace is deleted from another device while this screen is unfocused.
if (!isFocused) {
// Suppress the not-found view when the user has moved away from the workspace flow (e.g. switched
// to another tab and the workspace was deleted from another device) so the view doesn't bleed
// through over the active tab. Stays true when an RHP is open on top of a workspace screen.
if (!isWorkspacesTabFocused) {
return false;
}

Expand All @@ -181,7 +184,7 @@ function WorkspacePageWithSections({
// We check isPendingDelete and prevIsPendingDelete to prevent the NotFound view from showing right after we delete the workspace
return (!isEmptyObject(policy) && !canEditWorkspaceSettings(policy) && !shouldShowNonAdmin) || (!shouldShowPolicy && !(isPendingDelete && !prevIsPendingDelete));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isFocused, policy, shouldShowNonAdmin, shouldShowPolicy]);
}, [isWorkspacesTabFocused, policy, shouldShowNonAdmin, shouldShowPolicy]);

const handleOnBackButtonPress = () => {
if (shouldShow) {
Expand Down
Loading