Skip to content

Commit 5fda328

Browse files
authored
Merge pull request #89599 from callstack-internal/decompose-distance-and-report-steps
[Payment due @ShridharGoel] Decompose distance and report step into hooks
2 parents 2ca5f6a + c387beb commit 5fda328

20 files changed

Lines changed: 1661 additions & 669 deletions

config/eslint/eslint.seatbelt.tsv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@
555555
"../../src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
556556
"../../src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx" "react-hooks/set-state-in-effect" 1
557557
"../../src/pages/iou/request/step/IOURequestStepMerchant.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
558-
"../../src/pages/iou/request/step/IOURequestStepReport.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 2
558+
"../../src/pages/iou/request/step/IOURequestStepReport/hooks/useReportSelectionActions.ts" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 2
559559
"../../src/pages/iou/request/step/IOURequestStepScan/ReceiptView/index.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
560560
"../../src/pages/iou/request/step/IOURequestStepScan/ReceiptView/index.tsx" "react-hooks/set-state-in-effect" 1
561561
"../../src/pages/iou/request/step/IOURequestStepScan/components/MobileWebCameraView.tsx" "react-hooks/immutability" 1

src/pages/iou/request/step/IOURequestStepDistance.tsx

Lines changed: 28 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -27,39 +27,37 @@ import useReportIsArchived from '@hooks/useReportIsArchived';
2727
import useSelfDMReport from '@hooks/useSelfDMReport';
2828
import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep';
2929
import useWaypointItems from '@hooks/useWaypointItems';
30-
import {setMoneyRequestAmount, setMoneyRequestDistance} from '@libs/actions/IOU';
31-
import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyRequest';
32-
import {setDraftSplitTransaction, setSplitShares} from '@libs/actions/IOU/Split';
30+
import {setMoneyRequestDistance} from '@libs/actions/IOU';
31+
import {setDraftSplitTransaction} from '@libs/actions/IOU/Split';
3332
import {updateMoneyRequestDistance} from '@libs/actions/IOU/UpdateMoneyRequest';
3433
import {init, stop} from '@libs/actions/MapboxToken';
35-
import {openReport} from '@libs/actions/Report';
3634
import {openDraftDistanceExpense, removeWaypoint, updateWaypoints as updateWaypointsUtil} from '@libs/actions/Transaction';
37-
import {createBackupTransaction, removeBackupTransaction, restoreOriginalTransactionFromBackup} from '@libs/actions/TransactionEdit';
35+
import {removeBackupTransaction} from '@libs/actions/TransactionEdit';
3836
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
39-
import type {MileageRate} from '@libs/DistanceRequestUtils';
4037
import {getLatestErrorField} from '@libs/ErrorUtils';
4138
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
4239
import {shouldUseTransactionDraft} from '@libs/IOUUtils';
4340
import Navigation from '@libs/Navigation/Navigation';
4441
import OnyxTabNavigator, {TabScreenWithFocusTrapWrapper, TopTab} from '@libs/Navigation/OnyxTabNavigator';
4542
import {roundToTwoDecimalPlaces} from '@libs/NumberUtils';
4643
import {isPolicyExpenseChat as isPolicyExpenseChatUtil} from '@libs/ReportUtils';
47-
import {getDistanceInMeters, getRateID, getRequestType, hasRoute, haveWaypointAddressesChanged, isCustomUnitRateIDForP2P, isWaypointNullIsland} from '@libs/TransactionUtils';
44+
import {getDistanceInMeters, getRateID, getRequestType, haveWaypointAddressesChanged} from '@libs/TransactionUtils';
4845
import CONST from '@src/CONST';
4946
import type {IOUType} from '@src/CONST';
5047
import ONYXKEYS from '@src/ONYXKEYS';
5148
import ROUTES from '@src/ROUTES';
5249
import type SCREENS from '@src/SCREENS';
53-
import {hasSeenTourSelector} from '@src/selectors/Onboarding';
54-
import {validTransactionDraftIDsSelector} from '@src/selectors/TransactionDraft';
55-
import type {Participant} from '@src/types/onyx/IOU';
5650
import type {Errors} from '@src/types/onyx/OnyxCommon';
57-
import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
51+
import type {WaypointCollection} from '@src/types/onyx/Transaction';
5852
import type Transaction from '@src/types/onyx/Transaction';
5953
import {isEmptyObject} from '@src/types/utils/EmptyObject';
6054
import type TransactionStateType from '@src/types/utils/TransactionStateType';
6155
import DistanceManualTabContent from './DistanceManualTabContent';
6256
import DistanceMapTabContent from './DistanceMapTabContent';
57+
import useDistanceNavigation from './IOURequestStepDistance/hooks/useDistanceNavigation';
58+
import useDistanceRequestData from './IOURequestStepDistance/hooks/useDistanceRequestData';
59+
import useDistanceTransactionBackup from './IOURequestStepDistance/hooks/useDistanceTransactionBackup';
60+
import useWaypointValidation, {isWaypointEmpty} from './IOURequestStepDistance/hooks/useWaypointValidation';
6361
import StepScreenWrapper from './StepScreenWrapper';
6462
import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
6563
import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
@@ -97,20 +95,11 @@ function IOURequestStepDistance({
9795
const personalPolicy = usePersonalPolicy();
9896
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
9997
const defaultExpensePolicy = useDefaultExpensePolicy();
100-
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
101-
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
102-
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
10398
const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`);
104-
const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES);
105-
const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE);
10699
const [optimisticWaypoints, setOptimisticWaypoints] = useState<WaypointCollection | null>(null);
107-
const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES);
108100
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
109101
const {policyForMovingExpenses} = usePolicyForMovingExpenses();
110102
const [betas] = useOnyx(ONYXKEYS.BETAS);
111-
const [draftTransactionIDs] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, {selector: validTransactionDraftIDsSelector});
112-
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
113-
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
114103

115104
const isEditing = action === CONST.IOU.ACTION.EDIT;
116105
const isEditingSplit = (iouType === CONST.IOU.TYPE.SPLIT || iouType === CONST.IOU.TYPE.SPLIT_EXPENSE) && isEditing;
@@ -154,26 +143,11 @@ function IOURequestStepDistance({
154143
const isSplitRequest = iouType === CONST.IOU.TYPE.SPLIT;
155144
const hasRouteError = !!currentTransaction?.errorFields?.route;
156145
const [shouldShowAtLeastTwoDifferentWaypointsError, setShouldShowAtLeastTwoDifferentWaypointsError] = useState(false);
157-
const isWaypointEmpty = (waypoint?: Waypoint) => {
158-
if (!waypoint) {
159-
return true;
160-
}
161-
const {keyForList, ...waypointWithoutKey} = waypoint;
162-
return isEmpty(waypointWithoutKey);
163-
};
164-
const nonEmptyWaypointsCount = useMemo(() => Object.keys(waypoints).filter((key) => !isWaypointEmpty(waypoints[key])).length, [waypoints]);
165146
const currentUserAccountIDParam = currentUserPersonalDetails.accountID;
166147
const currentUserEmailParam = currentUserPersonalDetails.login ?? '';
167-
const isWaypointsNullIslandError = useMemo(() => Object.values(waypoints).some(isWaypointNullIsland), [waypoints]);
168-
const duplicateWaypointsError = useMemo(
169-
() => nonEmptyWaypointsCount >= 2 && Object.keys(validatedWaypoints).length !== nonEmptyWaypointsCount,
170-
[nonEmptyWaypointsCount, validatedWaypoints],
171-
);
172-
const atLeastTwoDifferentWaypointsError = useMemo(() => Object.keys(validatedWaypoints).length < 2, [validatedWaypoints]);
173-
const transactionWasSaved = useRef(false);
148+
const {nonEmptyWaypointsCount, isWaypointsNullIslandError, duplicateWaypointsError, atLeastTwoDifferentWaypointsError} = useWaypointValidation({waypoints, validatedWaypoints});
174149
const isCreatingNewRequest = !(backTo || isEditing);
175150
const [recentWaypoints, {status: recentWaypointsStatus}] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS);
176-
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
177151
const iouRequestType = getRequestType(currentTransaction);
178152
const customUnitRateID = getRateID(currentTransaction);
179153

@@ -229,32 +203,7 @@ function IOURequestStepDistance({
229203
lastSyncedRouteDistance.current = routeDistance;
230204
}, [routeDistance, distanceUnit]);
231205

232-
// Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants
233-
const setDistanceRequestData = useCallback(
234-
(participants: Participant[]) => {
235-
// Get policy report based on transaction participants
236-
const isPolicyExpenseChat = participants?.some((participant) => participant.isPolicyExpenseChat);
237-
const policyCurrency = policy?.outputCurrency ?? personalPolicy?.outputCurrency ?? CONST.CURRENCY.USD;
238-
239-
const policyMileageRates = DistanceRequestUtils.getMileageRates(policy);
240-
const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy);
241-
const selectedMileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction)
242-
? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction)
243-
: (customUnitRateID && policyMileageRates?.[customUnitRateID]) || defaultMileageRate;
244-
245-
const {unit, rate} = selectedMileageRate ?? {};
246-
const distance = getDistanceInMeters(transaction, unit);
247-
const currency = selectedMileageRate?.currency ?? policyCurrency;
248-
const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0);
249-
setMoneyRequestAmount(transactionID, amount, currency);
250-
251-
const participantAccountIDs: number[] | undefined = participants?.map((participant) => Number(participant.accountID ?? CONST.DEFAULT_NUMBER_ID));
252-
if (isSplitRequest && amount && currency && !isPolicyExpenseChat) {
253-
setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []);
254-
}
255-
},
256-
[policy, personalPolicy?.outputCurrency, transaction, customUnitRateID, transactionID, isSplitRequest],
257-
);
206+
const setDistanceRequestData = useDistanceRequestData({policy, personalPolicy, transaction, customUnitRateID, transactionID, isSplitRequest});
258207

259208
// For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace
260209
// request and the workspace requires a category or a tag
@@ -303,35 +252,16 @@ function IOURequestStepDistance({
303252
setShouldShowAtLeastTwoDifferentWaypointsError(false);
304253
}, [atLeastTwoDifferentWaypointsError, duplicateWaypointsError, hasRouteError, isLoading, isLoadingRoute, nonEmptyWaypointsCount, transaction]);
305254

306-
// This effect runs when the component is mounted and unmounted. It's purpose is to be able to properly
307-
// discard changes if the user cancels out of making any changes. This is accomplished by backing up the
308-
// original transaction, letting the user modify the current transaction, and then if the user ever
309-
// cancels out of the modal without saving changes, the original transaction is restored from the backup.
310-
useEffect(() => {
311-
if (isCreatingNewRequest || isEditingSplit) {
312-
return () => {};
313-
}
314-
const isDraft = shouldUseTransactionDraft(action);
315-
// On mount, create the backup transaction.
316-
createBackupTransaction(transaction, isDraft);
317-
318-
return () => {
319-
// If the user cancels out of the modal without saving changes, then the original transaction
320-
// needs to be restored from the backup so that all changes are removed.
321-
if (transactionWasSaved.current) {
322-
removeBackupTransaction(transaction?.transactionID);
323-
return;
324-
}
325-
restoreOriginalTransactionFromBackup(transaction?.transactionID, isDraft);
326-
327-
// If the user opens IOURequestStepDistance in offline mode and then goes online, re-open the report to fill in missing fields from the transaction backup
328-
if (!transaction?.reportID || hasRoute(transaction, true)) {
329-
return;
330-
}
331-
openReport({reportID: transaction?.reportID, introSelected, betas});
332-
};
333-
// eslint-disable-next-line react-hooks/exhaustive-deps
334-
}, []);
255+
const transactionWasSaved = useRef(false);
256+
useDistanceTransactionBackup({
257+
transaction,
258+
isCreatingNewRequest,
259+
isEditingSplit,
260+
isDraft: shouldUseTransactionDraft(action),
261+
introSelected,
262+
betas,
263+
transactionWasSavedRef: transactionWasSaved,
264+
});
335265

336266
const navigateBack = useCallback(() => {
337267
Navigation.goBack(backTo);
@@ -371,46 +301,7 @@ function IOURequestStepDistance({
371301
[action, iouType, transactionID, report?.reportID, reportID, backTo, isEditingSplit, isEditing],
372302
);
373303

374-
const navigateToNextStep = useCallback(() => {
375-
handleMoneyRequestStepDistanceNavigation({
376-
iouType,
377-
report,
378-
policy,
379-
transaction,
380-
reportID,
381-
transactionID,
382-
reportAttributesDerived,
383-
personalDetails,
384-
waypoints,
385-
currentUserLogin: currentUserEmailParam,
386-
currentUserAccountID: currentUserAccountIDParam,
387-
backTo,
388-
backToReport,
389-
shouldSkipConfirmation,
390-
defaultExpensePolicy,
391-
isArchivedExpenseReport: isArchived,
392-
isAutoReporting: !!personalPolicy?.autoReporting,
393-
isASAPSubmitBetaEnabled,
394-
transactionViolations,
395-
lastSelectedDistanceRates,
396-
setDistanceRequestData,
397-
translate,
398-
quickAction,
399-
policyRecentlyUsedCurrencies,
400-
introSelected,
401-
privateIsArchived: isArchived,
402-
selfDMReport,
403-
policyForMovingExpenses,
404-
betas,
405-
recentWaypoints,
406-
draftTransactionIDs,
407-
isSelfTourViewed: !!isSelfTourViewed,
408-
amountOwed,
409-
userBillingGracePeriodEnds,
410-
ownerBillingGracePeriodEnd,
411-
conciergeReportID,
412-
});
413-
}, [
304+
const navigateToNextStep = useDistanceNavigation({
414305
iouType,
415306
report,
416307
policy,
@@ -420,33 +311,23 @@ function IOURequestStepDistance({
420311
reportAttributesDerived,
421312
personalDetails,
422313
waypoints,
423-
currentUserEmailParam,
424-
currentUserAccountIDParam,
314+
currentUserLogin: currentUserEmailParam,
315+
currentUserAccountID: currentUserAccountIDParam,
425316
backTo,
426317
backToReport,
427318
shouldSkipConfirmation,
428319
defaultExpensePolicy,
429320
isArchived,
430-
personalPolicy?.autoReporting,
321+
isAutoReporting: !!personalPolicy?.autoReporting,
431322
isASAPSubmitBetaEnabled,
432-
transactionViolations,
433-
lastSelectedDistanceRates,
434323
setDistanceRequestData,
435324
translate,
436-
quickAction,
437-
policyRecentlyUsedCurrencies,
438-
introSelected,
439-
policyForMovingExpenses,
440325
selfDMReport,
326+
policyForMovingExpenses,
441327
betas,
442328
recentWaypoints,
443-
draftTransactionIDs,
444-
isSelfTourViewed,
445-
amountOwed,
446-
userBillingGracePeriodEnds,
447-
ownerBillingGracePeriodEnd,
448-
conciergeReportID,
449-
]);
329+
introSelected,
330+
});
450331

451332
const getError = useCallback(() => {
452333
// Get route error if available else show the invalid number of waypoints error.

0 commit comments

Comments
 (0)