Skip to content

Commit 4e5b268

Browse files
authored
Merge pull request Expensify#86424 from dukenv0307/fix/66424-part-12
2 parents 9ff5e0f + 63a5970 commit 4e5b268

7 files changed

Lines changed: 99 additions & 22 deletions

File tree

src/DeepLinkHandler.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Log from './libs/Log';
1111
import {endSpan} from './libs/telemetry/activeSpans';
1212
import ONYXKEYS from './ONYXKEYS';
1313
import type {Route} from './ROUTES';
14+
import {hasSeenTourSelector} from './selectors/Onboarding';
1415
import isLoadingOnyxValue from './types/utils/isLoadingOnyxValue';
1516

1617
type DeepLinkHandlerProps = {
@@ -30,11 +31,12 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {
3031
const [, sessionMetadata] = useOnyx(ONYXKEYS.SESSION);
3132
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
3233
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
34+
const [isSelfTourViewed, isSelfTourViewedMetadata] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
3335
const [betas] = useOnyx(ONYXKEYS.BETAS);
3436
const isAuthenticated = useIsAuthenticated();
3537

3638
useEffect(() => {
37-
if (isLoadingOnyxValue(sessionMetadata)) {
39+
if (isLoadingOnyxValue(sessionMetadata, isSelfTourViewedMetadata)) {
3840
return;
3941
}
4042
// If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report
@@ -48,7 +50,7 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {
4850
if (introSelected === undefined) {
4951
Log.info('[Deep link] introSelected is undefined when processing initial URL', false, {url});
5052
}
51-
openReportFromDeepLink(url, allReports, isAuthenticated, conciergeReportID, introSelected, betas);
53+
openReportFromDeepLink(url, allReports, isAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas);
5254
} else {
5355
Report.doneCheckingPublicRoom();
5456
}
@@ -65,14 +67,14 @@ function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) {
6567
Log.info('[Deep link] introSelected is undefined when processing URL change', false, {url: state.url});
6668
}
6769
const isCurrentlyAuthenticated = hasAuthToken();
68-
openReportFromDeepLink(state.url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected, betas);
70+
openReportFromDeepLink(state.url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected, isSelfTourViewed, betas);
6971
});
7072

7173
return () => {
7274
linkingChangeListener.current?.remove();
7375
};
74-
// eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this effect to re-run when conciergeReportID changes
75-
}, [sessionMetadata?.status, conciergeReportID, introSelected, betas]);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally excluding allReports, isAuthenticated, and onInitialUrl to avoid re-triggering deep link handling on every report update
77+
}, [sessionMetadata?.status, conciergeReportID, introSelected, isSelfTourViewedMetadata, betas]);
7678

7779
return null;
7880
}

src/components/WalletStatementModal/index.native.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {hasSeenTourSelector} from '@selectors/Onboarding';
12
import React, {useCallback, useRef} from 'react';
23
import type {WebViewMessageEvent, WebViewNavigation} from 'react-native-webview';
34
import {WebView} from 'react-native-webview';
@@ -22,6 +23,7 @@ function WalletStatementModal({statementPageURL}: WalletStatementProps) {
2223

2324
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
2425
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
26+
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
2527
const [betas] = useOnyx(ONYXKEYS.BETAS);
2628
const onMessage = useCallback(
2729
(event: WebViewMessageEvent) => {
@@ -32,12 +34,12 @@ function WalletStatementModal({statementPageURL}: WalletStatementProps) {
3234
return;
3335
}
3436

35-
handleWalletStatementNavigation(conciergeReportID, introSelected, session?.accountID, betas, type, url);
37+
handleWalletStatementNavigation(conciergeReportID, introSelected, session?.accountID, isSelfTourViewed, betas, type, url);
3638
} catch (error) {
3739
console.error('Error parsing message from WebView:', error);
3840
}
3941
},
40-
[conciergeReportID, session?.accountID, introSelected, betas],
42+
[conciergeReportID, session?.accountID, introSelected, isSelfTourViewed, betas],
4143
);
4244

4345
return (

src/components/WalletStatementModal/index.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React, {useState} from 'react';
1+
import {hasSeenTourSelector} from '@selectors/Onboarding';
2+
import React, {useCallback, useEffect, useRef, useState} from 'react';
23
import {View} from 'react-native';
34
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
45
import useOnyx from '@hooks/useOnyx';
@@ -15,15 +16,25 @@ function WalletStatementModal({statementPageURL}: WalletStatementProps) {
1516

1617
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
1718
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
19+
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
1820
const [betas] = useOnyx(ONYXKEYS.BETAS);
21+
const navigateRef = useRef<(event: MessageEvent<WalletStatementMessage>) => void>(null);
22+
1923
/**
2024
* Handles in-app navigation for iframe links
2125
*/
22-
const navigate = (event: MessageEvent<WalletStatementMessage>) => {
23-
const {data} = event;
24-
const {type, url} = data || {};
25-
handleWalletStatementNavigation(conciergeReportID, introSelected, session?.accountID, betas, type, url);
26-
};
26+
const navigate = useCallback(
27+
(event: MessageEvent<WalletStatementMessage>) => {
28+
const {data} = event;
29+
const {type, url} = data || {};
30+
handleWalletStatementNavigation(conciergeReportID, introSelected, session?.accountID, isSelfTourViewed, betas, type, url);
31+
},
32+
[conciergeReportID, introSelected, session?.accountID, isSelfTourViewed, betas],
33+
);
34+
35+
useEffect(() => {
36+
navigateRef.current = navigate;
37+
}, [navigate]);
2738

2839
return (
2940
<>
@@ -42,7 +53,7 @@ function WalletStatementModal({statementPageURL}: WalletStatementProps) {
4253

4354
// We listen to a message sent from the iframe to the parent component when a link is clicked.
4455
// This lets us handle navigation in the app, outside of the iframe.
45-
window.onmessage = navigate;
56+
window.onmessage = (event: MessageEvent<WalletStatementMessage>) => navigateRef.current?.(event);
4657
}}
4758
/>
4859
</View>

src/components/WalletStatementModal/walletNavigationUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function handleWalletStatementNavigation(
1515
conciergeReportID: string | undefined,
1616
introSelected: OnyxEntry<IntroSelected>,
1717
currentUserAccountID: number | undefined,
18+
isSelfTourViewed: boolean | undefined,
1819
betas: OnyxEntry<Beta[]>,
1920
type?: string,
2021
url?: string,
@@ -24,8 +25,7 @@ function handleWalletStatementNavigation(
2425
}
2526

2627
if (type === CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE) {
27-
// TODO: We'll pass isSelfTourViewed in the next PR. Refactor issue: https://github.com/Expensify/App/issues/66424
28-
navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, undefined, betas);
28+
navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, isSelfTourViewed, betas);
2929
return;
3030
}
3131

src/libs/actions/Link.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ function openReportFromDeepLink(
238238
isAuthenticated: boolean,
239239
conciergeReportID: string | undefined,
240240
introSelected: OnyxEntry<IntroSelected>,
241+
isSelfTourViewed: boolean | undefined,
241242
betas: OnyxEntry<Beta[]>,
242243
) {
243244
const reportID = getReportIDFromLink(url);
@@ -342,8 +343,7 @@ function openReportFromDeepLink(
342343
Navigation.navigate(lastAccessedReportRoute, {forceReplace: Navigation.getTopmostReportId() === reportID});
343344
return;
344345
}
345-
// TODO: We'll pass isSelfTourViewed in the next PR. Refactor issue: https://github.com/Expensify/App/issues/66424
346-
navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, undefined, betas, false, () => true);
346+
navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas, false, () => true);
347347
return;
348348
}
349349

tests/actions/ReportTest.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5953,7 +5953,7 @@ describe('actions/Report', () => {
59535953
const testIntroSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.ADMIN};
59545954
const TEST_USER_ACCOUNT_ID = 1;
59555955
expect(() => {
5956-
handleWalletStatementNavigation('123', testIntroSelected, TEST_USER_ACCOUNT_ID, undefined, 'invalidType', undefined);
5956+
handleWalletStatementNavigation('123', testIntroSelected, TEST_USER_ACCOUNT_ID, undefined, undefined, 'invalidType', undefined);
59575957
}).not.toThrow();
59585958
});
59595959

@@ -5964,7 +5964,7 @@ describe('actions/Report', () => {
59645964
const testIntroSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.ADMIN};
59655965
const TEST_USER_ACCOUNT_ID = 1;
59665966

5967-
handleWalletStatementNavigation('123', testIntroSelected, TEST_USER_ACCOUNT_ID, undefined, CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE, undefined);
5967+
handleWalletStatementNavigation('123', testIntroSelected, TEST_USER_ACCOUNT_ID, undefined, undefined, CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE, undefined);
59685968

59695969
await waitForBatchedUpdates();
59705970

@@ -5975,7 +5975,38 @@ describe('actions/Report', () => {
59755975
it('should not throw with undefined introSelected', () => {
59765976
const TEST_USER_ACCOUNT_ID = 1;
59775977
expect(() => {
5978-
handleWalletStatementNavigation('123', undefined, TEST_USER_ACCOUNT_ID, undefined, CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE, undefined);
5978+
handleWalletStatementNavigation('123', undefined, TEST_USER_ACCOUNT_ID, undefined, undefined, CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE, undefined);
5979+
}).not.toThrow();
5980+
});
5981+
5982+
it('should pass isSelfTourViewed=true to navigateToConciergeChat when selfTour has been viewed', async () => {
5983+
const mockNavigateToConciergeChat = jest.fn();
5984+
jest.mock('@userActions/Report', () => ({
5985+
...jest.requireActual<Record<string, unknown>>('@userActions/Report'),
5986+
navigateToConciergeChat: mockNavigateToConciergeChat,
5987+
}));
5988+
5989+
jest.resetModules();
5990+
const localHandleWalletStatementNavigation = jest.requireActual<{default: typeof handleWalletStatementNavigationDefault}>(
5991+
'@components/WalletStatementModal/walletNavigationUtils',
5992+
).default;
5993+
5994+
const testIntroSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.ADMIN};
5995+
const TEST_USER_ACCOUNT_ID = 1;
5996+
const isSelfTourViewed = true;
5997+
5998+
expect(() => {
5999+
localHandleWalletStatementNavigation('123', testIntroSelected, TEST_USER_ACCOUNT_ID, isSelfTourViewed, undefined, CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE, undefined);
6000+
}).not.toThrow();
6001+
});
6002+
6003+
it('should pass isSelfTourViewed=false to navigateToConciergeChat when selfTour has not been viewed', () => {
6004+
const testIntroSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.ADMIN};
6005+
const TEST_USER_ACCOUNT_ID = 1;
6006+
const isSelfTourViewed = false;
6007+
6008+
expect(() => {
6009+
handleWalletStatementNavigation('123', testIntroSelected, TEST_USER_ACCOUNT_ID, isSelfTourViewed, undefined, CONST.WALLET.WEB_MESSAGE_TYPE.CONCIERGE, undefined);
59796010
}).not.toThrow();
59806011
});
59816012
});

tests/unit/OnboardingSelectorsTest.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {hasCompletedGuidedSetupFlowSelector} from '@selectors/Onboarding';
1+
import {hasCompletedGuidedSetupFlowSelector, hasSeenTourSelector} from '@selectors/Onboarding';
22
import type {OnyxValue} from 'react-native-onyx';
33
import CONST from '@src/CONST';
44
import type ONYXKEYS from '@src/ONYXKEYS';
@@ -28,4 +28,35 @@ describe('onboardingSelectors', () => {
2828
expect(hasCompletedGuidedSetupFlowSelector(onboarding)).toBe(true);
2929
});
3030
});
31+
32+
describe('hasSeenTourSelector', () => {
33+
it('Should return false if onboarding NVP is an empty object', () => {
34+
const onboarding = {} as OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>;
35+
expect(hasSeenTourSelector(onboarding)).toBe(false);
36+
});
37+
38+
it('Should return false if onboarding NVP is undefined (treated as empty)', () => {
39+
expect(hasSeenTourSelector(undefined)).toBe(false);
40+
});
41+
42+
it('Should return false if onboarding NVP has selfTourViewed = false', () => {
43+
const onboarding = {selfTourViewed: false} as OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>;
44+
expect(hasSeenTourSelector(onboarding)).toBe(false);
45+
});
46+
47+
it('Should return true if onboarding NVP has selfTourViewed = true', () => {
48+
const onboarding = {selfTourViewed: true} as OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>;
49+
expect(hasSeenTourSelector(onboarding)).toBe(true);
50+
});
51+
52+
it('Should return false if onboarding NVP has no selfTourViewed field', () => {
53+
const onboarding = {hasCompletedGuidedSetupFlow: true} as OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>;
54+
expect(hasSeenTourSelector(onboarding)).toBe(false);
55+
});
56+
57+
it('Should return false if onboarding NVP contains only signupQualifier', () => {
58+
const onboarding = {signupQualifier: CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB} as OnyxValue<typeof ONYXKEYS.NVP_ONBOARDING>;
59+
expect(hasSeenTourSelector(onboarding)).toBe(false);
60+
});
61+
});
3162
});

0 commit comments

Comments
 (0)