Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent} from 'r

/* eslint-disable rulesdir/prefer-early-return */
import {useIsFocused, useRoute} from '@react-navigation/native';
import {guidedSetupAndTourStatusSelector} from '@selectors/Onboarding';
import isEmpty from 'lodash/isEmpty';
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import {DeviceEventEmitter, View} from 'react-native';
Expand Down Expand Up @@ -170,6 +171,7 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`);
const [guidedSetupAndTourStatus] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: guidedSetupAndTourStatusSelector});

const transactionThreadReportID = getOneTransactionThreadReportID(report, chatReport, reportActions ?? [], false, reportTransactionIDs);
const firstVisibleReportActionID = useMemo(() => getFirstVisibleReportActionID(reportActions, isOffline), [reportActions, isOffline]);
Expand Down Expand Up @@ -684,15 +686,35 @@ function MoneyRequestReportActionsList({onLayout}: MoneyRequestReportListProps)
}, 2000);

if (!hasNewestReportAction) {
openReport({reportID, introSelected, conciergeChat, betas, hasReportActions: true, currentUserAccountID});
openReport({
reportID,
introSelected,
conciergeChat,
betas,
hasReportActions: true,
currentUserAccountID,
isSelfTourViewed: guidedSetupAndTourStatus?.isSelfTourViewed,
hasCompletedGuidedSetupFlow: guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
});
scrollToBottom();
return;
}

// Defer marking the report as read until the scroll actually reaches the bottom (handled in onTrackScrolling).
pendingMarkAsReadRef.current = true;
scrollToBottom();
}, [setIsFloatingMessageCounterVisible, hasNewestReportAction, scrollToBottom, reportID, introSelected, conciergeChat, betas, currentUserAccountID]);
}, [
setIsFloatingMessageCounterVisible,
hasNewestReportAction,
scrollToBottom,
reportID,
introSelected,
conciergeChat,
betas,
currentUserAccountID,
guidedSetupAndTourStatus?.isSelfTourViewed,
guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
]);

useEffect(() => {
return () => {
Expand Down
13 changes: 12 additions & 1 deletion src/hooks/useReportActionsScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {NativeScrollEvent, NativeSyntheticEvent, ViewToken} from 'react-nat
import type {OnyxEntry} from 'react-native-onyx';

import {useRoute} from '@react-navigation/native';
import {guidedSetupAndTourStatusSelector} from '@selectors/Onboarding';
import {useEffect, useEffectEvent, useState} from 'react';

import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
Expand Down Expand Up @@ -167,6 +168,7 @@ function useReportActionsScroll({
const {isOffline} = useNetworkWithOfflineStatus();
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [guidedSetupAndTourStatus] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: guidedSetupAndTourStatusSelector});
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const [reportLoadingState] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${reportID}`);
const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${getNonEmptyStringOnyxID(reportID)}`);
Expand Down Expand Up @@ -352,7 +354,16 @@ function useReportActionsScroll({
if (!Navigation.getReportRHPActiveRoute()) {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID, undefined, undefined, backTo));
}
openReport({reportID, introSelected, conciergeChat, betas, hasReportActions: true, currentUserAccountID});
openReport({
reportID,
introSelected,
conciergeChat,
betas,
hasReportActions: true,
currentUserAccountID,
isSelfTourViewed: guidedSetupAndTourStatus?.isSelfTourViewed,
hasCompletedGuidedSetupFlow: guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
});
reportScrollManager.scrollToBottom();
return;
}
Expand Down
13 changes: 12 additions & 1 deletion src/pages/Search/SearchMoneyRequestReportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import type {Transaction, TransactionViolations} from '@src/types/onyx';

import {PortalHost} from '@gorhom/portal';
import {useIsFocused} from '@react-navigation/native';
import {guidedSetupAndTourStatusSelector} from '@selectors/Onboarding';
import React, {useEffect, useMemo, useRef} from 'react';

type SearchMoneyRequestPageProps =
Expand Down Expand Up @@ -128,6 +129,7 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`);
const [guidedSetupAndTourStatus] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: guidedSetupAndTourStatusSelector});
const {transactions: allReportTransactions, violations: allReportViolations} = useTransactionsAndViolationsForReport(reportIDFromRoute);
const {transactionThreadReportID, effectiveTransactionThreadReportID, reportActions} = useTransactionThreadReportID(reportIDFromRoute);
const reportTransactions = useMemo(() => getAllNonDeletedTransactions(allReportTransactions, reportActions), [allReportTransactions, reportActions]);
Expand Down Expand Up @@ -215,7 +217,16 @@ function SearchMoneyRequestReportPage({route}: SearchMoneyRequestPageProps) {
return;
}

openReport({reportID: reportIDFromRoute, introSelected, conciergeChat, betas, hasReportActions, currentUserAccountID});
openReport({
reportID: reportIDFromRoute,
introSelected,
conciergeChat,
betas,
hasReportActions,
currentUserAccountID,
isSelfTourViewed: guidedSetupAndTourStatus?.isSelfTourViewed,
hasCompletedGuidedSetupFlow: guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
Comment thread
daledah marked this conversation as resolved.
});
isInitialMountRef.current = false;

// oneTransactionID dependency handles the case when deleting a transaction:
Expand Down
4 changes: 4 additions & 0 deletions src/pages/Share/ShareDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import KeyboardUtils from '@src/utils/keyboard';
import type {StackScreenProps} from '@react-navigation/stack';
import type {OnyxEntry} from 'react-native-onyx';

import {guidedSetupAndTourStatusSelector} from '@selectors/Onboarding';
import {isDraftReportSelector} from '@selectors/Report';
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
Expand All @@ -63,6 +64,7 @@ function ShareDetailsPage({route}: ShareDetailsPageProps) {
const [currentAttachment] = useOnyx(ONYXKEYS.SHARE_TEMP_FILE);
const [validatedFile] = useOnyx(ONYXKEYS.VALIDATED_FILE_OBJECT);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [guidedSetupAndTourStatus] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: guidedSetupAndTourStatusSelector});
const [isDraftReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportOrAccountID}`, {selector: isDraftReportSelector});
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
Expand Down Expand Up @@ -177,6 +179,8 @@ function ShareDetailsPage({route}: ShareDetailsPageProps) {
betas,
hasReportActions: false,
currentUserAccountID: personalDetail.accountID,
isSelfTourViewed: guidedSetupAndTourStatus?.isSelfTourViewed,
hasCompletedGuidedSetupFlow: guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
});
}
if (report.reportID) {
Expand Down
25 changes: 23 additions & 2 deletions src/pages/TransactionDuplicate/DynamicReviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {Transaction} from '@src/types/onyx';
import getEmptyArray from '@src/types/utils/getEmptyArray';

import {useFocusEffect, useRoute} from '@react-navigation/native';
import {guidedSetupAndTourStatusSelector} from '@selectors/Onboarding';
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';

Expand Down Expand Up @@ -67,6 +68,7 @@ function DynamicReviewPage() {
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`);
const [guidedSetupAndTourStatus] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: guidedSetupAndTourStatusSelector});

const originalTransactionIDsListRef = useRef<string[] | null>(null);

Expand Down Expand Up @@ -120,8 +122,27 @@ function DynamicReviewPage() {
if (!route.params.reportID || report?.reportID) {
return;
}
openReport({reportID: route.params.reportID, introSelected, conciergeChat, betas, hasReportActions, currentUserAccountID: currentPersonalDetails.accountID});
}, [report?.reportID, route.params.reportID, introSelected, conciergeChat, betas, hasReportActions, currentPersonalDetails.accountID]);
openReport({
reportID: route.params.reportID,
introSelected,
conciergeChat,
betas,
hasReportActions,
currentUserAccountID: currentPersonalDetails.accountID,
isSelfTourViewed: guidedSetupAndTourStatus?.isSelfTourViewed,
hasCompletedGuidedSetupFlow: guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
});
}, [
report?.reportID,
route.params.reportID,
introSelected,
conciergeChat,
betas,
hasReportActions,
currentPersonalDetails.accountID,
guidedSetupAndTourStatus?.isSelfTourViewed,
guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
Comment thread
daledah marked this conversation as resolved.
]);

useEffect(() => {
if (!transactionID) {
Expand Down
4 changes: 4 additions & 0 deletions src/pages/workspace/rooms/WorkspaceRoomsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';

import {useFocusEffect} from '@react-navigation/native';
import {guidedSetupAndTourStatusSelector} from '@selectors/Onboarding';
import {policyChatRoomsSelector} from '@selectors/Report';
import React from 'react';
import {View} from 'react-native';
Expand All @@ -57,6 +58,7 @@ function WorkspaceRoomsPage({route}: WorkspaceRoomsPageProps) {
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [conciergeChat] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${conciergeReportID}`);
const [guidedSetupAndTourStatus] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: guidedSetupAndTourStatusSelector});
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();

const [policyReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: policyChatRoomsSelector(policyID, reportNameValuePairs)});
Expand Down Expand Up @@ -97,6 +99,8 @@ function WorkspaceRoomsPage({route}: WorkspaceRoomsPageProps) {
shouldMarkAsRead: false,
hasReportActions: !!hasReportActions?.[report.reportID],
currentUserAccountID,
isSelfTourViewed: guidedSetupAndTourStatus?.isSelfTourViewed,
hasCompletedGuidedSetupFlow: guidedSetupAndTourStatus?.hasCompletedGuidedSetupFlow,
});
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.REPORT_DETAILS.getRoute(report.reportID)));
return;
Expand Down
25 changes: 24 additions & 1 deletion tests/unit/OnboardingSelectorsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type ONYXKEYS from '@src/ONYXKEYS';

import type {OnyxValue} from 'react-native-onyx';

import {hasCompletedGuidedSetupFlowSelector, hasSeenTourSelector, isTrackIntentUserSelector} from '@selectors/Onboarding';
import {guidedSetupAndTourStatusSelector, hasCompletedGuidedSetupFlowSelector, hasSeenTourSelector, isTrackIntentUserSelector} from '@selectors/Onboarding';

import createMock from '../utils/createMock';

Expand Down Expand Up @@ -93,4 +93,27 @@ describe('onboardingSelectors', () => {
expect(isTrackIntentUserSelector(introSelected)).toBe(false);
});
});

// The combined selector derives both onboarding flags from a single NVP_ONBOARDING read. Callers that need both
// (e.g. the openReport wiring) rely on each field mapping to its own source without being swapped.
describe('guidedSetupAndTourStatusSelector', () => {
it('Should map each flag from its own source without swapping them', () => {
const onboarding = createMock<NonNullable<OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>>>({selfTourViewed: true, hasCompletedGuidedSetupFlow: false});
expect(guidedSetupAndTourStatusSelector(onboarding)).toEqual({isSelfTourViewed: true, hasCompletedGuidedSetupFlow: false});
});

it('Should treat an empty onboarding NVP as tour-not-seen and guided-setup-completed', () => {
const onboarding = createMock<NonNullable<OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>>>({});
expect(guidedSetupAndTourStatusSelector(onboarding)).toEqual({isSelfTourViewed: false, hasCompletedGuidedSetupFlow: true});
});

it('Should treat an undefined onboarding NVP the same as an empty one', () => {
expect(guidedSetupAndTourStatusSelector(undefined)).toEqual({isSelfTourViewed: false, hasCompletedGuidedSetupFlow: true});
});

it('Should reflect a fully completed onboarding (tour seen and guided setup done)', () => {
const onboarding = createMock<NonNullable<OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>>>({selfTourViewed: true, hasCompletedGuidedSetupFlow: true});
expect(guidedSetupAndTourStatusSelector(onboarding)).toEqual({isSelfTourViewed: true, hasCompletedGuidedSetupFlow: true});
});
});
});
Loading