Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: three not found view #36409

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7344aec
fix three pane not found view
kosmydel Feb 13, 2024
dd61617
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Feb 19, 2024
c9d6bb3
cleanup
kosmydel Feb 19, 2024
d87748b
fix not authorized screen
kosmydel Feb 19, 2024
39ecf1e
fix not found views - new approach
kosmydel Feb 19, 2024
54f989d
cleanup
kosmydel Feb 19, 2024
5c69724
fix going back from settings list
kosmydel Feb 19, 2024
f90c15a
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Feb 26, 2024
3857afb
cleanup
kosmydel Feb 26, 2024
05748d4
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 4, 2024
0fdab42
fix: multiple not authorized pages, internal deeplinking
kosmydel Mar 4, 2024
1ee9491
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 5, 2024
dcf885a
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 7, 2024
06fc5b3
use isNavigationReady
kosmydel Mar 7, 2024
cefa01d
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 11, 2024
036b97c
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 13, 2024
3ce078a
support ideal nav v2
kosmydel Mar 13, 2024
f1743db
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 21, 2024
31e4969
Merge branch 'Expensify:main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 21, 2024
2ef9669
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 25, 2024
cceb280
Merge branch 'Expensify:main' into @kosmydel/duplicated-not-found-view
kosmydel Mar 25, 2024
5680de7
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Apr 3, 2024
a93c787
restart typecheck
kosmydel Apr 3, 2024
5c92eff
Merge branch 'main' into @kosmydel/duplicated-not-found-view
kosmydel Apr 11, 2024
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
7 changes: 7 additions & 0 deletions src/libs/Navigation/Navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {Report} from '@src/types/onyx';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import originalDismissModal from './dismissModal';
import originalDismissModalWithReport from './dismissModalWithReport';
import originalDismissRHP from './dismissRHP';
import originalGetTopmostReportActionId from './getTopmostReportActionID';
import originalGetTopmostReportId from './getTopmostReportId';
import linkingConfig from './linkingConfig';
Expand Down Expand Up @@ -61,6 +62,11 @@ const dismissModal = (reportID?: string, ref = navigationRef) => {
originalDismissModalWithReport({reportID, ...report}, ref);
};

// Re-exporting the dismissRHP here to fill in default value for navigationRef. The dismissRHP isn't defined in this file to avoid cyclic dependencies.
const dismissRHP = (ref = navigationRef) => {
originalDismissRHP(ref);
};

// Re-exporting the dismissModalWithReport here to fill in default value for navigationRef. The dismissModalWithReport isn't defined in this file to avoid cyclic dependencies.
// This method is needed because it allows to dismiss the modal and then open the report. Within this method is checked whether the report belongs to a specific workspace. Sometimes the report we want to check, hasn't been added to the Onyx yet.
// Then we can pass the report as a param without getting it from the Onyx.
Expand Down Expand Up @@ -363,6 +369,7 @@ export default {
setShouldPopAllStateOnUP,
navigate,
setParams,
dismissRHP,
dismissModal,
dismissModalWithReport,
isActiveRoute,
Expand Down
25 changes: 25 additions & 0 deletions src/libs/Navigation/dismissRHP.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type {NavigationContainerRef} from '@react-navigation/native';
import {StackActions} from '@react-navigation/native';
import NAVIGATORS from '@src/NAVIGATORS';
import type {RootStackParamList} from './types';

// This function is in a separate file than Navigation.ts to avoid cyclic dependency.

/**
* Dismisses the RHP modal stack if there is any
*
* @param targetReportID - The reportID to navigate to after dismissing the modal
*/
function dismissRHP(navigationRef: NavigationContainerRef<RootStackParamList>) {
if (!navigationRef.isReady()) {
return;
}

const state = navigationRef.getState();
const lastRoute = state.routes.at(-1);
if (lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) {
navigationRef.dispatch({...StackActions.pop(), target: state.key});
}
}

export default dismissRHP;
5 changes: 4 additions & 1 deletion src/pages/ErrorPage/NotFoundPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import ScreenWrapper from '@components/ScreenWrapper';

type NotFoundPageProps = {
onBackButtonPress?: () => void;

shouldForceFullScreen?: boolean;
};

// eslint-disable-next-line rulesdir/no-negated-variables
function NotFoundPage({onBackButtonPress}: NotFoundPageProps) {
function NotFoundPage({onBackButtonPress, shouldForceFullScreen}: NotFoundPageProps) {
return (
<ScreenWrapper testID={NotFoundPage.displayName}>
<FullPageNotFoundView
shouldShow
onBackButtonPress={onBackButtonPress}
shouldForceFullScreen={shouldForceFullScreen}
/>
</ScreenWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ function AdminPolicyAccessOrNotFoundComponent(props: AdminPolicyAccessOrNotFound
}

if (shouldShowNotFoundPage) {
return <NotFoundPage onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))} />;
return (
<NotFoundPage
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))}
shouldForceFullScreen
/>
);
}

return typeof props.children === 'function' ? props.children(props) : props.children;
Expand Down
7 changes: 6 additions & 1 deletion src/pages/workspace/PaidPolicyAccessOrNotFoundWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ function PaidPolicyAccessOrNotFoundComponent(props: PaidPolicyAccessOrNotFoundCo
}

if (shouldShowNotFoundPage) {
return <NotFoundPage onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))} />;
return (
<NotFoundPage
onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))}
shouldForceFullScreen
/>
);
}

return typeof props.children === 'function' ? props.children(props) : props.children;
Expand Down
14 changes: 14 additions & 0 deletions src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,20 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
// We check isPendingDelete for both policy and prevPolicy to prevent the NotFound view from showing right after we delete the workspace
(PolicyUtils.isPendingDeletePolicy(policy) && PolicyUtils.isPendingDeletePolicy(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;
}
// We are dismissing any modals that are open when the NotFound view is shown
Navigation.isNavigationReady().then(() => {
Navigation.dismissRHP();
});
}, [canAccessRoute, policy, 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 @@ -81,6 +81,9 @@ function WorkspaceInviteMessagePage({workspaceInviteMessageDraft, invitedEmailsT
setWelcomeNote(parser.htmlToMarkdown(getDefaultWelcomeNote()));
return;
}
if (isEmptyObject(policy)) {
return;
}
Navigation.goBack(ROUTES.WORKSPACE_INVITE.getRoute(route.params.policyID), true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
129 changes: 55 additions & 74 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 @@ -47,6 +44,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 = {
/** Personal details of all users */
Expand Down Expand Up @@ -75,16 +73,7 @@ function invertObject(object: Record<string, string>): Record<string, string> {

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

function WorkspaceMembersPage({
policyMembers,
personalDetails,
invitedEmailsToAccountIDsDraft,
route,
policy,
session,
currentUserPersonalDetails,
isLoadingReportData = true,
}: WorkspaceMembersPageProps) {
function WorkspaceMembersPage({policyMembers, personalDetails, invitedEmailsToAccountIDsDraft, route, policy, session, currentUserPersonalDetails}: WorkspaceMembersPageProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const [selectedEmployees, setSelectedEmployees] = useState<number[]>([]);
Expand Down Expand Up @@ -544,71 +533,63 @@ function WorkspaceMembersPage({
};

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={translate('workspace.people.removeMembersPrompt')}
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}
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
headerMessage={getHeaderMessage()}
headerContent={getHeaderContent()}
onSelectRow={openMemberDetails}
onCheckboxPress={(item) => toggleUser(item.accountID)}
onSelectAll={() => toggleAllUsers(data)}
onDismissError={dismissError}
showLoadingPlaceholder={isLoading}
showScrollIndicator
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
textInputRef={textInputRef}
customListHeader={getCustomListHeader()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
{() => (
<>
{isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{getHeaderButtons()}</View>}
<ConfirmModal
danger
title={translate('workspace.people.removeMembersTitle')}
isVisible={removeMembersConfirmModalVisible}
onConfirm={removeUsers}
onCancel={() => setRemoveMembersConfirmModalVisible(false)}
prompt={translate('workspace.people.removeMembersPrompt')}
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}
disableKeyboardShortcuts={removeMembersConfirmModalVisible}
headerMessage={getHeaderMessage()}
headerContent={getHeaderContent()}
onSelectRow={openMemberDetails}
onCheckboxPress={(item) => toggleUser(item.accountID)}
onSelectAll={() => toggleAllUsers(data)}
onDismissError={dismissError}
showLoadingPlaceholder={isLoading}
showScrollIndicator
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
textInputRef={textInputRef}
customListHeader={getCustomListHeader()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
/>
</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
Loading