Skip to content

Commit af2710c

Browse files
authored
Merge pull request #90302 from callstack-internal/feat/79048-restore
feat: Refator USD flow to use useSubPage
2 parents cd9d85c + 220b7d3 commit af2710c

65 files changed

Lines changed: 1011 additions & 510 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/CONST/index.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,65 @@ const CONST = {
666666
VALIDATION: 'ValidationStep',
667667
ENABLE: 'EnableStep',
668668
},
669+
PAGE_NAMES: {
670+
COUNTRY: 'currency-and-country',
671+
BANK_ACCOUNT: 'bank-info',
672+
REQUESTOR: 'personal-info',
673+
VERIFY_IDENTITY: 'verify-identity',
674+
COMPANY: 'company',
675+
BENEFICIAL_OWNERS: 'business-owner',
676+
ACH_CONTRACT: 'ach-contract',
677+
VALIDATION: 'validation',
678+
ENABLE: 'enable',
679+
},
669680
STEP_NAMES: ['1', '2', '3', '4', '5', '6'],
681+
BANK_INFO_STEP: {
682+
SUB_PAGE_NAMES: {
683+
MANUAL: 'manual',
684+
PLAID: 'plaid',
685+
},
686+
},
687+
PERSONAL_INFO_STEP: {
688+
SUB_PAGE_NAMES: {
689+
FULL_NAME: 'full-name',
690+
DATE_OF_BIRTH: 'date-of-birth',
691+
SSN: 'ssn',
692+
ADDRESS: 'address',
693+
CONFIRMATION: 'confirmation',
694+
},
695+
},
696+
BUSINESS_INFO_STEP: {
697+
SUB_PAGE_NAMES: {
698+
NAME: 'name',
699+
TAX_ID: 'tax-id',
700+
WEBSITE: 'website',
701+
PHONE: 'phone',
702+
ADDRESS: 'address',
703+
TYPE: 'type',
704+
INCORPORATION_DATE: 'start-date',
705+
INCORPORATION_STATE: 'state',
706+
INCORPORATION_CODE: 'code',
707+
CONFIRMATION: 'confirmation',
708+
},
709+
},
710+
BENEFICIAL_OWNERS_STEP: {
711+
SUB_PAGE_NAMES: {
712+
IS_USER_UBO: 'is-user-ubo',
713+
IS_ANYONE_ELSE_UBO: 'is-anyone-else-ubo',
714+
ARE_THERE_MORE_UBOS: 'are-there-more-ubos',
715+
UBOS_LIST: 'ubos-list',
716+
LEGAL_NAME: 'legal-name',
717+
DATE_OF_BIRTH: 'date-of-birth',
718+
SSN: 'ssn',
719+
ADDRESS: 'address',
720+
CONFIRMATION: 'confirmation',
721+
},
722+
},
723+
COMPLETE_VERIFICATION_STEP: {
724+
SUB_PAGE_NAMES: {
725+
CONFIRM_AGREEMENTS: 'confirm-agreements',
726+
},
727+
},
670728
SUBSTEP: {
671729
MANUAL: 'manual',
672730
PLAID: 'plaid',

src/ROUTES.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import type {IOUAction, IOURequestType, IOUType, OdometerImageType} from './CONS
1313
import type {ReplacementReason} from './libs/actions/Card';
1414
import Log from './libs/Log';
1515
import type {RootNavigatorParamList} from './libs/Navigation/types';
16-
import type {ReimbursementAccountStepToOpen} from './libs/ReimbursementAccountUtils';
1716
import StringUtils from './libs/StringUtils';
1817
import {getUrlWithParams} from './libs/Url';
1918
import SCREENS from './SCREENS';
@@ -864,34 +863,20 @@ const ROUTES = {
864863

865864
getRoute: (policyID?: string, backTo?: string) => getUrlWithBackToParam(`bank-account/${VERIFY_ACCOUNT}?policyID=${policyID}`, backTo),
866865
},
867-
BANK_ACCOUNT_NEW: 'bank-account/new',
868866
BANK_ACCOUNT_PERSONAL: 'bank-account/personal',
867+
// TODO: rename the route as no longer accepts step
869868
BANK_ACCOUNT_WITH_STEP_TO_OPEN: {
870-
route: 'bank-account/:stepToOpen?',
871-
getRoute: ({
872-
policyID,
873-
stepToOpen = '',
874-
bankAccountID,
875-
backTo,
876-
subStepToOpen,
877-
}: {
878-
policyID: string | undefined;
879-
stepToOpen?: ReimbursementAccountStepToOpen;
880-
bankAccountID?: number;
881-
backTo?: string;
882-
subStepToOpen?: typeof CONST.BANK_ACCOUNT.STEP.COUNTRY;
883-
}) => {
884-
if (!policyID && !bankAccountID) {
885-
return getUrlWithBackToParam(`bank-account/${stepToOpen}`, backTo);
886-
}
887-
869+
route: 'bank-account/new',
870+
getRoute: ({policyID, bankAccountID, backTo}: {policyID: string | undefined; bankAccountID?: number; backTo?: string}) => {
871+
let queryString = '';
888872
if (bankAccountID) {
889-
return getUrlWithBackToParam(`bank-account/${stepToOpen}?bankAccountID=${bankAccountID}`, backTo);
873+
queryString = `?bankAccountID=${bankAccountID}`;
874+
} else if (policyID) {
875+
queryString = `?policyID=${policyID}`;
890876
}
891877
// TODO this backTo comes from drilling it through bank account form screens
892878
// should be removed once https://github.com/Expensify/App/pull/72219 is resolved
893-
894-
return getUrlWithBackToParam(`bank-account/${stepToOpen}?policyID=${policyID}${subStepToOpen ? `&subStep=${subStepToOpen}` : ''}`, backTo);
879+
return getUrlWithBackToParam(`bank-account/new${queryString}`, backTo);
895880
},
896881
},
897882
BANK_ACCOUNT_ENTER_SIGNER_INFO: {
@@ -921,6 +906,18 @@ const ROUTES = {
921906
return getUrlWithBackToParam(`${base}${pagePart}${subPagePart}${actionPart}${queryString}`, backTo);
922907
},
923908
},
909+
BANK_ACCOUNT_USD_SETUP: {
910+
route: 'bank-account/new/us/:page?/:subPage?/:action?',
911+
getRoute: ({policyID, page, subPage, action, backTo}: {policyID?: string; page?: string; subPage?: string; action?: 'edit'; backTo?: string}) => {
912+
const base = 'bank-account/new/us';
913+
const pagePart = page ? `/${page}` : '';
914+
const subPagePart = subPage ? `/${subPage}` : '';
915+
const actionPart = action ? `/${action}` : '';
916+
const queryString = policyID ? `?policyID=${policyID}` : '';
917+
918+
return getUrlWithBackToParam(`${base}${pagePart}${subPagePart}${actionPart}${queryString}`, backTo);
919+
},
920+
},
924921
SETTINGS: 'settings',
925922
SETTINGS_PROFILE: {
926923
route: 'settings/profile',

src/SCREENS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ const SCREENS = {
10011001
DYNAMIC_PRIVATE_NOTES_LIST: 'Dynamic_PrivateNotes_List',
10021002
DYNAMIC_PRIVATE_NOTES_EDIT: 'Dynamic_PrivateNotes_Edit',
10031003
REIMBURSEMENT_ACCOUNT: 'ReimbursementAccount',
1004+
REIMBURSEMENT_ACCOUNT_USD: 'Reimbursement_Account_USD',
10041005
REIMBURSEMENT_ACCOUNT_NON_USD: 'Reimbursement_Account_Non_USD',
10051006
REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO: 'Reimbursement_Account_Signer_Info',
10061007
REFERRAL_DETAILS: 'Referral_Details',

src/components/DatePicker/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ function DatePicker({
175175
anchorPosition={popoverPosition}
176176
shouldPositionFromTop={!isInverted}
177177
forwardedFSClass={forwardedFSClass}
178+
shouldCloseWhenBrowserNavigationChanged
178179
/>
179180
</>
180181
);

src/components/Modal/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import useTheme from '@hooks/useTheme';
44
import StatusBar from '@libs/StatusBar';
55
import CONST from '@src/CONST';
66
import BaseModal from './BaseModal';
7+
import {withInternalPopstate} from './internalPopstateGuard';
78
import type BaseModalProps from './types';
89
import type {WindowState} from './types';
910

@@ -55,7 +56,7 @@ function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = (
5556
if (!(window.history.state as WindowState)?.shouldGoBack) {
5657
return;
5758
}
58-
window.history.back();
59+
withInternalPopstate(() => window.history.back());
5960
}, 0);
6061
} else {
6162
onModalHide();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
let isInternal = false;
2+
3+
/** Returns true when the next `popstate` was triggered by our own `history.back()`, not a real user back navigation. */
4+
function isInternalPopstateInProgress(): boolean {
5+
return isInternal;
6+
}
7+
8+
/** Runs `action` (e.g. `history.back()`) while flagging the resulting `popstate` as internal, so listeners can ignore it. */
9+
function withInternalPopstate(action: () => void) {
10+
isInternal = true;
11+
const clear = () => {
12+
isInternal = false;
13+
window.removeEventListener('popstate', clear);
14+
};
15+
window.addEventListener('popstate', clear);
16+
action();
17+
}
18+
19+
export {isInternalPopstateInProgress, withInternalPopstate};

src/components/Navigation/DebugTabView.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
2121
import getFocusedLeafScreenName from '@libs/Navigation/helpers/getFocusedLeafScreenName';
2222
import isTabRouteAtRoot from '@libs/Navigation/helpers/isTabRouteAtRoot';
2323
import Navigation from '@libs/Navigation/Navigation';
24-
import {getRouteForCurrentStep as getReimbursementAccountRouteForCurrentStep} from '@libs/ReimbursementAccountUtils';
2524
import {getChatTabBrickRoadReportID} from '@libs/WorkspacesSettingsUtils';
2625
import variables from '@styles/variables';
2726
import CONST from '@src/CONST';
@@ -97,10 +96,7 @@ function getSettingsRoute(status: IndicatorStatus | undefined, reimbursementAcco
9796
case CONST.INDICATOR_STATUS.HAS_POLICY_ERRORS:
9897
return ROUTES.WORKSPACE_INITIAL.getRoute(policyIDWithErrors);
9998
case CONST.INDICATOR_STATUS.HAS_REIMBURSEMENT_ACCOUNT_ERRORS:
100-
return ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute({
101-
policyID: reimbursementAccount?.achData?.policyID,
102-
stepToOpen: getReimbursementAccountRouteForCurrentStep(reimbursementAccount?.achData?.currentStep ?? CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT),
103-
});
99+
return ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute({policyID: reimbursementAccount?.achData?.policyID});
104100
case CONST.INDICATOR_STATUS.HAS_SUBSCRIPTION_ERRORS:
105101
return ROUTES.SETTINGS_SUBSCRIPTION.route;
106102
case CONST.INDICATOR_STATUS.HAS_SUBSCRIPTION_INFO:

src/components/Popover/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, {useRef} from 'react';
22
import {createPortal} from 'react-dom';
33
import Modal from '@components/Modal';
4+
import {isInternalPopstateInProgress} from '@components/Modal/internalPopstateGuard';
45
import {usePopoverActions, usePopoverState} from '@components/PopoverProvider';
56
import PopoverWithoutOverlay from '@components/PopoverWithoutOverlay';
67
import useResponsiveLayout from '@hooks/useResponsiveLayout';
@@ -59,6 +60,11 @@ function Popover(props: PopoverProps) {
5960
if (!isVisible) {
6061
return;
6162
}
63+
// A nested Modal closing itself fires history.back() to drop its guard entry;
64+
// that popstate is not a real user navigation, so skip closing.
65+
if (isInternalPopstateInProgress()) {
66+
return;
67+
}
6268
onClose?.();
6369
};
6470
window.addEventListener('popstate', listener);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {useCallback, useEffect, useRef} from 'react';
2+
import ONYXKEYS from '@src/ONYXKEYS';
3+
import useOnyx from './useOnyx';
4+
5+
/**
6+
* Defers navigation (onSubmit) until the reimbursement account API call completes.
7+
* Instead of navigating to the next step immediately after firing the API call,
8+
* this hook waits for `isLoading` to go back to `false` and checks for errors.
9+
*
10+
* @param onSubmit - callback that navigates to the next step
11+
* @returns markSubmitting - call this right after firing the API action
12+
*/
13+
export default function useReimbursementAccountSubmitCallback(onSubmit?: () => void) {
14+
const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
15+
const isSubmittingRef = useRef(false);
16+
17+
useEffect(() => {
18+
if (!isSubmittingRef.current || reimbursementAccount?.isLoading || reimbursementAccount?.errors) {
19+
return;
20+
}
21+
isSubmittingRef.current = false;
22+
onSubmit?.();
23+
}, [reimbursementAccount?.isLoading, reimbursementAccount?.errors, onSubmit]);
24+
25+
return useCallback(() => {
26+
isSubmittingRef.current = true;
27+
}, []);
28+
}

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {useCallback} from 'react';
33
import {View} from 'react-native';
44
import useResponsiveLayout from '@hooks/useResponsiveLayout';
55
import useThemeStyles from '@hooks/useThemeStyles';
6+
import {isMobileChrome} from '@libs/Browser';
67
import withAgentAccessDenied from '@libs/Navigation/AppNavigator/withAgentAccessDenied';
78
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
89
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
@@ -64,6 +65,16 @@ const loadAttachmentModalScreen = () => require<ReactComponentModule>('../../../
6465

6566
type Screens = Partial<Record<Screen, () => React.ComponentType>>;
6667

68+
const IS_MOBILE_CHROME = isMobileChrome();
69+
70+
const REIMBURSEMENT_ACCOUNT_FLOW_SCREENS: Screen[] = [
71+
SCREENS.REIMBURSEMENT_ACCOUNT,
72+
SCREENS.REIMBURSEMENT_ACCOUNT_USD,
73+
SCREENS.REIMBURSEMENT_ACCOUNT_NON_USD,
74+
SCREENS.REIMBURSEMENT_ACCOUNT_VERIFY_ACCOUNT,
75+
SCREENS.REIMBURSEMENT_ACCOUNT_ENTER_SIGNER_INFO,
76+
];
77+
6778
const OPTIONS_PER_SCREEN: Partial<Record<Screen, PlatformStackNavigationOptions>> = {
6879
[SCREENS.SETTINGS.MERGE_ACCOUNTS.MERGE_RESULT]: {
6980
animationTypeForReplace: 'push',
@@ -131,6 +142,9 @@ const OPTIONS_PER_SCREEN: Partial<Record<Screen, PlatformStackNavigationOptions>
131142
[SCREENS.TWO_FACTOR_AUTH.SUCCESS]: {
132143
animationTypeForReplace: 'push',
133144
},
145+
// Reimbursement Account flow animations glitch on low-end Android devices in Chrome mWeb so we intentionally disable them here.
146+
// see https://github.com/Expensify/App/issues/87658
147+
...(IS_MOBILE_CHROME ? Object.fromEntries(REIMBURSEMENT_ACCOUNT_FLOW_SCREENS.map((screen) => [screen, {animation: Animations.NONE}])) : {}),
134148
};
135149

136150
/**
@@ -740,6 +754,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
740754
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_DESKTOP_ITEMS]: () => require<ReactComponentModule>('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage').default,
741755
[SCREENS.CONNECT_EXISTING_BUSINESS_BANK_ACCOUNT_ROOT]: () => require<ReactComponentModule>('@pages/workspace/ConnectExistingBusinessBankAccountPage').default,
742756
[SCREENS.REIMBURSEMENT_ACCOUNT]: withAgentAccessDenied(() => require<ReactComponentModule>('../../../../pages/ReimbursementAccount/ReimbursementAccountPage').default),
757+
[SCREENS.REIMBURSEMENT_ACCOUNT_USD]: () => require<ReactComponentModule>('../../../../pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlowPage').default,
743758
[SCREENS.REIMBURSEMENT_ACCOUNT_NON_USD]: withAgentAccessDenied(
744759
() => require<ReactComponentModule>('../../../../pages/ReimbursementAccount/NonUSD/NonUSDVerifiedBankAccountFlowPage').default,
745760
),

0 commit comments

Comments
 (0)