Skip to content

Commit

Permalink
Merge pull request #41665 from software-mansion-labs/@kosmydel/not-fo…
Browse files Browse the repository at this point in the history
…und-view-v2

Fix: three not found view v2
  • Loading branch information
neil-marcellini authored Jul 23, 2024
2 parents 91a5c69 + e60668b commit 7aed308
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 81 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2380,6 +2380,7 @@ const CONST = {
WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows',
WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount',
WORKSPACE_SETTINGS: 'WorkspaceSettings',
WORKSPACE_FEATURES: 'WorkspaceFeatures',
},
get EXPENSIFY_EMAILS() {
return [
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/linkTo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export default function linkTo(navigation: NavigationContainerRef<RootStackParam
}
}
// All actions related to FullScreenNavigator on wide screen are pushed when comparing differences between rootState and adaptedState.
if (action.payload.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR && !isNarrowLayout) {
if (action.payload.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR) {
return;
}
action.type = CONST.NAVIGATION.ACTION_TYPE.PUSH;
Expand Down
7 changes: 1 addition & 6 deletions src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import getMatchingBottomTabRouteForState from './getMatchingBottomTabRouteForSta
import getMatchingCentralPaneRouteForState from './getMatchingCentralPaneRouteForState';
import replacePathInNestedState from './replacePathInNestedState';

const RHP_SCREENS_OPENED_FROM_LHN = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS] as const;
const RHP_SCREENS_OPENED_FROM_LHN = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS, SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE] satisfies Screen[];

type RHPScreenOpenedFromLHN = TupleToUnion<typeof RHP_SCREENS_OPENED_FROM_LHN>;

Expand Down Expand Up @@ -174,11 +174,6 @@ function getAdaptedState(state: PartialState<NavigationState<RootStackParamList>
const attachmentsScreen = state.routes.find((route) => route.name === SCREENS.ATTACHMENTS);
const featureTrainingModalNavigator = state.routes.find((route) => route.name === NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR);

if (isNarrowLayout) {
metainfo.isFullScreenNavigatorMandatory = false;
metainfo.isCentralPaneAndBottomTabMandatory = false;
}

if (rhpNavigator) {
// Routes
// - matching bottom tab
Expand Down
4 changes: 2 additions & 2 deletions src/pages/workspace/AccessOrNotFoundWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN
return shouldShowFullScreenFallback ? (
<FullPageNotFoundView
shouldShow
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WORKSPACES)}
onBackButtonPress={() => Navigation.dismissModal()}
shouldForceFullScreen
// eslint-disable-next-line react/jsx-props-no-spreading
{...fullPageNotFoundViewProps}
Expand Down Expand Up @@ -155,7 +155,7 @@ function AccessOrNotFoundWrapper({accessVariants = [], fullPageNotFoundViewProps
return (
<PageNotFoundFallback
policyID={policyID}
shouldShowFullScreenFallback={!isFeatureEnabled}
shouldShowFullScreenFallback={!isFeatureEnabled || isPolicyNotAccessible}
fullPageNotFoundViewProps={fullPageNotFoundViewProps}
/>
);
Expand Down
21 changes: 20 additions & 1 deletion src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useFocusEffect, useNavigationState} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -97,6 +97,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
const activeRoute = useNavigationState(getTopmostRouteName);
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const wasRendered = useRef(false);

const prevPendingFields = usePrevious(policy?.pendingFields);
const policyFeatureStates = useMemo(
Expand Down Expand Up @@ -347,6 +348,24 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
PolicyUtils.goBackFromInvalidPolicy();
}, [policy, prevPolicy]);

// We are checking if the user can access the route.
// If user can't access the route, we are dismissing any modals that are open when the NotFound view is shown
const canAccessRoute = activeRoute && menuItems.some((item) => item.routeName === activeRoute);

useEffect(() => {
if (!shouldShowNotFoundPage && canAccessRoute) {
return;
}
if (wasRendered.current) {
return;
}
wasRendered.current = true;
// We are dismissing any modals that are open when the NotFound view is shown
Navigation.isNavigationReady().then(() => {
Navigation.closeRHPFlow();
});
}, [canAccessRoute, shouldShowNotFoundPage]);

const policyAvatar = useMemo(() => {
if (!policy) {
return {source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR};
Expand Down
3 changes: 3 additions & 0 deletions src/pages/workspace/WorkspaceInviteMessagePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ function WorkspaceInviteMessagePage({
setWelcomeNote(getDefaultWelcomeNote());
return;
}
if (isEmptyObject(policy)) {
return;
}
Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID), true);
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, []);
Expand Down
125 changes: 57 additions & 68 deletions src/pages/workspace/WorkspaceMembersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ import {InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import Badge from '@components/Badge';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption, WorkspaceMemberBulkActionType} from '@components/ButtonWithDropdownMenu/types';
import ConfirmModal from '@components/ConfirmModal';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import MessagesRow from '@components/MessagesRow';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import TableListItem from '@components/SelectionList/TableListItem';
import type {ListItem, SelectionListHandle} from '@components/SelectionList/types';
Expand Down Expand Up @@ -48,6 +45,7 @@ import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
import WorkspacePageWithSections from './WorkspacePageWithSections';

type WorkspaceMembersPageOnyxProps = {
/** Session info for the currently logged in user. */
Expand All @@ -71,7 +69,7 @@ function invertObject(object: Record<string, string>): Record<string, string> {

type MemberOption = Omit<ListItem, 'accountID'> & {accountID: number};

function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails, isLoadingReportData = true}: WorkspaceMembersPageProps) {
function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails}: WorkspaceMembersPageProps) {
const policyMemberEmailsToAccountIDs = useMemo(() => PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList, true), [policy?.employeeList]);
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -526,74 +524,65 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft,
};

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
<WorkspacePageWithSections
headerText={translate('workspace.common.members')}
route={route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
headerContent={!isSmallScreenWidth && getHeaderButtons()}
icon={Illustrations.ReceiptWrangler}
testID={WorkspaceMembersPage.displayName}
shouldShowLoading={false}
shouldShowOfflineIndicatorInWideScreen
shouldShowNonAdmin
>
<FullPageNotFoundView
shouldShow={(isEmptyObject(policy) && !isLoadingReportData) || PolicyUtils.isPendingDeletePolicy(policy)}
subtitleKey={isEmptyObject(policy) ? undefined : 'workspace.common.notAuthorized'}
onBackButtonPress={PolicyUtils.goBackFromInvalidPolicy}
onLinkPress={PolicyUtils.goBackFromInvalidPolicy}
>
<HeaderWithBackButton
title={translate('workspace.common.members')}
icon={Illustrations.ReceiptWrangler}
onBackButtonPress={() => {
Navigation.goBack();
}}
shouldShowBackButton={isSmallScreenWidth}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_MEMBERS}
>
{!isSmallScreenWidth && getHeaderButtons()}
</HeaderWithBackButton>
{isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{getHeaderButtons()}</View>}
<ConfirmModal
danger
title={translate('workspace.people.removeMembersTitle')}
isVisible={removeMembersConfirmModalVisible}
onConfirm={removeUsers}
onCancel={() => setRemoveMembersConfirmModalVisible(false)}
prompt={confirmModalPrompt}
confirmText={translate('common.remove')}
cancelText={translate('common.cancel')}
onModalHide={() => {
InteractionManager.runAfterInteractions(() => {
if (!textInputRef.current) {
return;
}
textInputRef.current.focus();
});
}}
/>
<View style={[styles.w100, styles.flex1]}>
<SelectionList
ref={selectionListRef}
canSelectMultiple={isPolicyAdmin}
sections={[{data, isDisabled: false}]}
ListItem={TableListItem}
shouldUseUserSkeletonView
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
headerMessage={getHeaderMessage()}
headerContent={!isSmallScreenWidth && getHeaderContent()}
onSelectRow={openMemberDetails}
shouldDebounceRowSelect={!isPolicyAdmin}
onCheckboxPress={(item) => toggleUser(item.accountID)}
onSelectAll={() => toggleAllUsers(data)}
onDismissError={dismissError}
showLoadingPlaceholder={isLoading}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
textInputRef={textInputRef}
customListHeader={getCustomListHeader()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
listHeaderContent={isSmallScreenWidth ? <View style={[styles.pl5, styles.pr5]}>{getHeaderContent()}</View> : null}
showScrollIndicator={false}
{() => (
<>
{isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{getHeaderButtons()}</View>}
<ConfirmModal
danger
title={translate('workspace.people.removeMembersTitle')}
isVisible={removeMembersConfirmModalVisible}
onConfirm={removeUsers}
onCancel={() => setRemoveMembersConfirmModalVisible(false)}
prompt={confirmModalPrompt}
confirmText={translate('common.remove')}
cancelText={translate('common.cancel')}
onModalHide={() => {
InteractionManager.runAfterInteractions(() => {
if (!textInputRef.current) {
return;
}
textInputRef.current.focus();
});
}}
/>
</View>
</FullPageNotFoundView>
</ScreenWrapper>
<View style={[styles.w100, styles.flex1]}>
<SelectionList
ref={selectionListRef}
canSelectMultiple={isPolicyAdmin}
sections={[{data, isDisabled: false}]}
ListItem={TableListItem}
shouldUseUserSkeletonView
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
headerMessage={getHeaderMessage()}
headerContent={!isSmallScreenWidth && getHeaderContent()}
onSelectRow={openMemberDetails}
shouldDebounceRowSelect={!isPolicyAdmin}
onCheckboxPress={(item) => toggleUser(item.accountID)}
onSelectAll={() => toggleAllUsers(data)}
onDismissError={dismissError}
showLoadingPlaceholder={isLoading}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
textInputRef={textInputRef}
customListHeader={getCustomListHeader()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
listHeaderContent={isSmallScreenWidth ? <View style={[styles.pl5, styles.pr5]}>{getHeaderContent()}</View> : null}
showScrollIndicator={false}
/>
</View>
</>
)}
</WorkspacePageWithSections>
);
}

Expand Down
14 changes: 12 additions & 2 deletions src/pages/workspace/WorkspacePageWithSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps &
* */
icon?: IconAsset;

/** Content to be added to the header */
headerContent?: ReactNode;

/** TestID of the component */
testID?: string;

/** Whether the page is loading, example any other API call in progres */
isLoading?: boolean;
};
Expand Down Expand Up @@ -112,6 +118,8 @@ function WorkspacePageWithSections({
shouldShowLoading = true,
shouldShowOfflineIndicatorInWideScreen = false,
shouldShowNonAdmin = false,
headerContent,
testID,
shouldShowNotFoundPage = false,
isLoading: isPageLoading = false,
}: WorkspacePageWithSectionsProps) {
Expand Down Expand Up @@ -160,7 +168,7 @@ function WorkspacePageWithSections({
includeSafeAreaPaddingBottom={false}
shouldEnablePickerAvoiding={false}
shouldEnableMaxHeight
testID={WorkspacePageWithSections.displayName}
testID={testID ?? WorkspacePageWithSections.displayName}
shouldShowOfflineIndicatorInWideScreen={shouldShowOfflineIndicatorInWideScreen && !shouldShow}
>
<FullPageNotFoundView
Expand All @@ -177,7 +185,9 @@ function WorkspacePageWithSections({
onBackButtonPress={() => Navigation.goBack(backButtonRoute)}
icon={icon ?? undefined}
style={styles.headerBarDesktopHeight}
/>
>
{headerContent}
</HeaderWithBackButton>
{(isLoading || firstRender.current) && shouldShowLoading && isFocused ? (
<FullScreenLoadingIndicator style={[styles.flex1, styles.pRelative]} />
) : (
Expand Down
4 changes: 3 additions & 1 deletion src/pages/workspace/WorkspaceProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,13 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, currencyList = {
headerText={translate('workspace.common.profile')}
route={route}
guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_PROFILE}
shouldShowLoading={false}
// When we create a new workspaces, the policy prop will not be set on the first render. Therefore, we have to delay rendering until it has been set in Onyx.
shouldShowLoading={policy === undefined}
shouldUseScrollView
shouldShowOfflineIndicatorInWideScreen
shouldShowNonAdmin
icon={Illustrations.House}
shouldShowNotFoundPage={policy === undefined}
>
{(hasVBA?: boolean) => (
<View style={[styles.flex1, styles.mt3, isSmallScreenWidth ? styles.workspaceSectionMobile : styles.workspaceSection]}>
Expand Down

0 comments on commit 7aed308

Please sign in to comment.