Skip to content
Closed
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
10 changes: 4 additions & 6 deletions src/pages/inbox/AccountManagerBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import Navigation from '@libs/Navigation/Navigation';
import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
import {getParticipantsAccountIDsForDisplay, isConciergeChatReport} from '@libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -23,16 +22,15 @@ function AccountManagerBanner({reportID}: AccountManagerBannerProps) {
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportID)}`);
const [accountManagerReportID] = useOnyx(ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID);
const [accountManagerReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(accountManagerReportID)}`);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const accountManagerAccountID = getParticipantsAccountIDsForDisplay(accountManagerReport, false, true)?.at(0) ?? -1;
const [participantPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {
selector: (details) => details?.[accountManagerAccountID],
});
const [isBannerVisible, setIsBannerVisible] = useState(true);

if (!accountManagerReportID || !isConciergeChatReport(report) || !isBannerVisible) {
return null;
}

const participants = getParticipantsAccountIDsForDisplay(accountManagerReport, false, true);
const participantPersonalDetails = getPersonalDetailsForAccountIDs([participants?.at(0) ?? -1], personalDetails);
const participantPersonalDetail = Object.values(participantPersonalDetails).at(0);
const displayName = getDisplayNameOrDefault(participantPersonalDetail);
const login = participantPersonalDetail?.login;
const chatWithAccountManagerText = displayName && login ? translate('common.chatWithAccountManager', `${displayName} (${login})`) : '';
Expand Down
95 changes: 87 additions & 8 deletions src/pages/inbox/ReportNotFoundGuard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useRoute} from '@react-navigation/native';
import type {ReactNode} from 'react';
import React, {useEffect} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
Expand All @@ -15,23 +16,31 @@ import {getParentReportActionDeletionStatus} from '@libs/TransactionNavigationUt
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {isLoadingInitialReportActionsSelector} from '@src/selectors/ReportMetaData';
import type * as OnyxTypes from '@src/types/onyx';

type ReportNotFoundGuardProps = {
children: ReactNode;
};

/**
* Two-level gate: the outer guard subscribes to lightweight keys to determine
* whether the report clearly exists. When it does (and the report is not a
* transaction thread), children render directly without the cost of
* parentReportMetadata / useParentReportAction subscriptions.
*
* The inner gate mounts only when the "not found" path is plausible:
* the report is missing, the path is invalid, or the report is a transaction
* thread whose parent action may have been deleted.
*/
// eslint-disable-next-line rulesdir/no-negated-variables
function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) {
const styles = useThemeStyles();
const route = useRoute();
const routeParams = route.params as {reportID?: string} | undefined;
const reportIDFromRoute = getNonEmptyStringOnyxID(routeParams?.reportID);

const {isOffline} = useNetwork();
const {shouldUseNarrowLayout} = useResponsiveLayout();

const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`);
const [parentReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.parentReportID}`);
const [userLeavingStatus = false] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportIDFromRoute}`);
const [isLoadingInitialReportActions = true] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {
selector: isLoadingInitialReportActionsSelector,
Expand All @@ -40,9 +49,81 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) {
const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP);
const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL);

const parentReportAction = useParentReportAction(report);
const reportID = report?.reportID;
const isOptimisticDelete = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
const isInvalidReportPath = !!routeParams?.reportID && !isValidReportIDFromPath(routeParams.reportID);
const isLoading = isLoadingApp !== false || isLoadingReportData || (!isOffline && !!isLoadingInitialReportActions);
const mayBeTransactionThread = isReportTransactionThread(report);

// Fast path: skip the expensive inner guard (parentReportMetadata, useParentReportAction)
// when we can determine shouldShowNotFoundPage is definitely false.
//
// shouldShowNotFoundPage is false when:
// - deleteTransactionNavigateBackUrl is set (always suppresses not-found), OR
// - path is valid AND report is not a transaction thread AND (still loading OR report exists)
const reportClearlyExists = !!reportID || isOptimisticDelete || userLeavingStatus;
const canSkipInnerGuard = !!deleteTransactionNavigateBackUrl || (!isInvalidReportPath && !mayBeTransactionThread && (isLoading || reportClearlyExists));
if (canSkipInnerGuard) {
return children;
}

return (
<ReportNotFoundInnerGuard
report={report}
reportIDFromPath={routeParams?.reportID}
reportID={reportID}
isOptimisticDelete={isOptimisticDelete}
isInvalidReportPath={isInvalidReportPath}
isLoading={isLoading}
isLoadingApp={isLoadingApp}
isLoadingReportData={isLoadingReportData}
isLoadingInitialReportActions={isLoadingInitialReportActions}
isOffline={isOffline}
userLeavingStatus={userLeavingStatus}
deleteTransactionNavigateBackUrl={deleteTransactionNavigateBackUrl}
>
{children}
</ReportNotFoundInnerGuard>
);
}

type ReportNotFoundInnerGuardProps = {
report: OnyxEntry<OnyxTypes.Report>;
reportIDFromPath: string | undefined;
reportID: string | undefined;
isOptimisticDelete: boolean;
isInvalidReportPath: boolean;
isLoading: boolean;
isLoadingApp: OnyxEntry<boolean>;
isLoadingReportData: boolean;
isLoadingInitialReportActions: boolean;
isOffline: boolean;
userLeavingStatus: boolean;
deleteTransactionNavigateBackUrl: OnyxEntry<string>;
children: ReactNode;
};

// eslint-disable-next-line rulesdir/no-negated-variables
function ReportNotFoundInnerGuard({
report,
reportIDFromPath,
reportID,
isOptimisticDelete,
isInvalidReportPath,
isLoading,
isLoadingApp,
isLoadingReportData,
isLoadingInitialReportActions,
isOffline,
userLeavingStatus,
deleteTransactionNavigateBackUrl,
children,
}: ReportNotFoundInnerGuardProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();

const [parentReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.parentReportID}`);
const parentReportAction = useParentReportAction(report);

const {isParentActionMissingAfterLoad, isParentActionDeleted} = getParentReportActionDeletionStatus({
parentReportID: report?.parentReportID,
Expand All @@ -53,8 +134,6 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) {
});
const isDeletedTransactionThread = isReportTransactionThread(report) && (isParentActionDeleted || isParentActionMissingAfterLoad);

const isInvalidReportPath = !!routeParams?.reportID && !isValidReportIDFromPath(routeParams.reportID);
const isLoading = isLoadingApp !== false || isLoadingReportData || (!isOffline && !!isLoadingInitialReportActions);
const reportExists = !!reportID || (!isDeletedTransactionThread && isOptimisticDelete) || userLeavingStatus;

// eslint-disable-next-line rulesdir/no-negated-variables
Expand All @@ -73,7 +152,7 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) {
reportID,
isOptimisticDelete,
userLeavingStatus,
reportIDFromPath: routeParams?.reportID,
reportIDFromPath,
deleteTransactionNavigateBackUrl,
isDeletedTransactionThread,
isParentActionDeleted,
Expand All @@ -88,7 +167,7 @@ function ReportNotFoundGuard({children}: ReportNotFoundGuardProps) {
reportID,
isOptimisticDelete,
userLeavingStatus,
routeParams?.reportID,
reportIDFromPath,
deleteTransactionNavigateBackUrl,
isDeletedTransactionThread,
isParentActionDeleted,
Expand Down
Loading