From 2d4ff632edbaaa70ecf12058834827b19969d3c5 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Sat, 19 Jul 2025 00:05:22 +0700 Subject: [PATCH 01/12] remove Onyx.connect --- src/libs/IOUUtils.ts | 10 +--------- .../request/step/IOURequestStepScan/index.native.tsx | 5 +++-- .../iou/request/step/IOURequestStepScan/index.tsx | 7 ++++--- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index e7b50ef35c791..caa0ca53b283f 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -1,8 +1,6 @@ -import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; @@ -14,12 +12,6 @@ import Performance from './Performance'; import {getReportTransactions} from './ReportUtils'; import {getCurrency, getTagArrayFromName} from './TransactionUtils'; -let lastLocationPermissionPrompt: string; -Onyx.connect({ - key: ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, - callback: (val) => (lastLocationPermissionPrompt = val ?? ''), -}); - function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: IOUType, transactionID: string, reportID: string, iouAction?: IOUAction): void { if (iouAction === CONST.IOU.ACTION.CATEGORIZE || iouAction === CONST.IOU.ACTION.SUBMIT || iouAction === CONST.IOU.ACTION.SHARE) { Navigation.goBack(); @@ -208,7 +200,7 @@ function formatCurrentUserToAttendee(currentUser?: PersonalDetails, reportID?: s return [initialAttendee]; } -function shouldStartLocationPermissionFlow() { +function shouldStartLocationPermissionFlow(lastLocationPermissionPrompt: string) { return ( !lastLocationPermissionPrompt || (DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') && diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 50d3f9b8d3e26..51da65eec1498 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -107,6 +107,7 @@ function IOURequestStepScan({ const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${initialTransactionID}`, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); + const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const platform = getPlatform(true); @@ -573,7 +574,7 @@ function IOURequestStepScan({ setReceiptFiles(newReceiptFiles); const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); @@ -591,7 +592,7 @@ function IOURequestStepScan({ if (shouldSkipConfirmation) { const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); return; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 8f0cb77b24869..6a78490e4def2 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -115,6 +115,7 @@ function IOURequestStepScan({ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); + const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); const isEditing = action === CONST.IOU.ACTION.EDIT; const canUseMultiScan = !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport; const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo); @@ -606,7 +607,7 @@ function IOURequestStepScan({ setReceiptFiles(newReceiptFiles); const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); @@ -659,7 +660,7 @@ function IOURequestStepScan({ if (shouldSkipConfirmation) { const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); return; @@ -668,7 +669,7 @@ function IOURequestStepScan({ } navigateToConfirmationStep(files, false); }, - [initialTransaction, iouType, navigateToConfirmationStep, shouldSkipConfirmation], + [initialTransaction, iouType, lastLocationPermissionPrompt, navigateToConfirmationStep, shouldSkipConfirmation], ); const getScreenshot = useCallback(() => { From 6a7b5687fbb0c61a2a4dea54b284762067f3f5a0 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Sat, 19 Jul 2025 00:09:01 +0700 Subject: [PATCH 02/12] add dependency --- src/pages/iou/request/step/IOURequestStepScan/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 51da65eec1498..4dacfb57e9596 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -601,7 +601,7 @@ function IOURequestStepScan({ } navigateToConfirmationStep(files, false); }, - [initialTransaction, iouType, navigateToConfirmationStep, shouldSkipConfirmation], + [initialTransaction, iouType, navigateToConfirmationStep, shouldSkipConfirmation, lastLocationPermissionPrompt], ); const capturePhoto = useCallback(() => { From b85b7eb1aadeda8468a953fd4ab17aaaa5b930dc Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Sat, 19 Jul 2025 00:17:57 +0700 Subject: [PATCH 03/12] make it cleaner --- src/libs/IOUUtils.ts | 6 +++--- .../iou/request/step/IOURequestStepScan/index.native.tsx | 4 ++-- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index caa0ca53b283f..5ace258a37107 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -200,11 +200,11 @@ function formatCurrentUserToAttendee(currentUser?: PersonalDetails, reportID?: s return [initialAttendee]; } -function shouldStartLocationPermissionFlow(lastLocationPermissionPrompt: string) { +function shouldStartLocationPermissionFlow(lastLocationPermissionPrompt?: string) { return ( !lastLocationPermissionPrompt || - (DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') && - DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt ?? '')) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS) + (DateUtils.isValidDateString(lastLocationPermissionPrompt) && + DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt)) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS) ); } diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 4dacfb57e9596..276beb7850075 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -574,7 +574,7 @@ function IOURequestStepScan({ setReceiptFiles(newReceiptFiles); const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); @@ -592,7 +592,7 @@ function IOURequestStepScan({ if (shouldSkipConfirmation) { const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); return; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 6a78490e4def2..7e2f2cae0c833 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -607,7 +607,7 @@ function IOURequestStepScan({ setReceiptFiles(newReceiptFiles); const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); @@ -660,7 +660,7 @@ function IOURequestStepScan({ if (shouldSkipConfirmation) { const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt ?? ''); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); return; From a02c2eb8f8d857a0942e532e829e713cc580ebb0 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Wed, 23 Jul 2025 17:20:07 +0700 Subject: [PATCH 04/12] add Uts --- tests/unit/IOUUtilsTest.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index acbf0e0c4408e..fdf50f830ae12 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -460,3 +460,37 @@ describe('Check valid amount for IOU/Expense request', () => { expect(unreportedAmount).toBeLessThan(0); }); }); + +describe('shouldStartLocationPermissionFlow', () => { + const now = new Date(); + const daysAgo = (days: number) => { + const d = new Date(now); + d.setDate(d.getDate() - days); + return d.toISOString(); + }; + + test('returns true if lastLocationPermissionPrompt is undefined', () => { + expect(IOUUtils.shouldStartLocationPermissionFlow(undefined)).toBe(true); + }); + + test('returns true if lastLocationPermissionPrompt is empty string', () => { + expect(IOUUtils.shouldStartLocationPermissionFlow('')).toBe(true); + }); + + test('returns false if lastLocationPermissionPrompt is a valid date string within threshold', () => { + // Use a date string less than threshold days ago + const recentDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS - 1); + expect(IOUUtils.shouldStartLocationPermissionFlow(recentDate)).toBe(false); + }); + + test('returns true if lastLocationPermissionPrompt is a valid date string outside threshold', () => { + // Use a date string more than threshold days ago + const oldDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS + 1); + expect(IOUUtils.shouldStartLocationPermissionFlow(oldDate)).toBe(true); + }); + + test('returns false if lastLocationPermissionPrompt is an invalid date string', () => { + // Should not start flow if the string is not a valid date + expect(IOUUtils.shouldStartLocationPermissionFlow('not-a-date')).toBe(false); + }); +}); From ffe9e845177d2e7e78aba0858350da9070c75c63 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 25 Jul 2025 11:12:57 +0700 Subject: [PATCH 05/12] update --max-warnings --- Mobile-Expensify | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 05d84fa3d3ee0..7597160132508 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 05d84fa3d3ee002cbd4e21c795be1f1adcf0a073 +Subproject commit 75971601325084a68a57d8e412168aa4d8b32afd diff --git a/package.json b/package.json index 173ad6ae8dfe4..44562293e350b 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=332 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=323 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", From 53fe687ac43a7a165c54e43a7524738da079ac7a Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 25 Jul 2025 11:15:13 +0700 Subject: [PATCH 06/12] revert mobile-expensify --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 7597160132508..05d84fa3d3ee0 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 75971601325084a68a57d8e412168aa4d8b32afd +Subproject commit 05d84fa3d3ee002cbd4e21c795be1f1adcf0a073 From ca87b52895dce74edd9409338f63d168d0d2a4d5 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Fri, 25 Jul 2025 12:16:09 +0700 Subject: [PATCH 07/12] update --max-warnings --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 44562293e350b..ed54193b4984d 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=323 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=326 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", From 3d9d8e6203d06ffc02718d61ce86f256edbd3c1c Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Sat, 26 Jul 2025 16:09:10 +0700 Subject: [PATCH 08/12] revert Mobile-Expensify --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 05d84fa3d3ee0..157beffb3e1d3 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 05d84fa3d3ee002cbd4e21c795be1f1adcf0a073 +Subproject commit 157beffb3e1d34a99938fa822881168df3e2a18b From 1c7c64b6eaf6f85f88f6ed89cd436d7e80248c57 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 28 Jul 2025 23:55:27 +0700 Subject: [PATCH 09/12] update to use custom hook --- src/hooks/useIOUUtils.ts | 19 +++ src/libs/IOUUtils.ts | 9 -- .../step/IOURequestStepScan/index.native.tsx | 11 +- .../request/step/IOURequestStepScan/index.tsx | 11 +- tests/unit/IOUUtilsTest.ts | 34 ----- tests/unit/useIOUUtilsTest.ts | 117 ++++++++++++++++++ 6 files changed, 148 insertions(+), 53 deletions(-) create mode 100644 src/hooks/useIOUUtils.ts create mode 100644 tests/unit/useIOUUtilsTest.ts diff --git a/src/hooks/useIOUUtils.ts b/src/hooks/useIOUUtils.ts new file mode 100644 index 0000000000000..12e7481cba2ff --- /dev/null +++ b/src/hooks/useIOUUtils.ts @@ -0,0 +1,19 @@ +import DateUtils from '@libs/DateUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import useOnyx from './useOnyx'; + +function useIOUUtils() { + const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); + function shouldStartLocationPermissionFlow() { + return ( + !lastLocationPermissionPrompt || + (DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') && + DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt ?? '')) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS) + ); + } + + return {shouldStartLocationPermissionFlow}; +} + +export default useIOUUtils; diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 5ace258a37107..65d0a7af29197 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -200,14 +200,6 @@ function formatCurrentUserToAttendee(currentUser?: PersonalDetails, reportID?: s return [initialAttendee]; } -function shouldStartLocationPermissionFlow(lastLocationPermissionPrompt?: string) { - return ( - !lastLocationPermissionPrompt || - (DateUtils.isValidDateString(lastLocationPermissionPrompt) && - DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt)) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS) - ); -} - export { calculateAmount, insertTagIntoTransactionTagsString, @@ -218,6 +210,5 @@ export { navigateToStartMoneyRequestStep, updateIOUOwnerAndTotal, formatCurrentUserToAttendee, - shouldStartLocationPermissionFlow, navigateToParticipantPage, }; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index e5a9236dd9515..3d4c9c7b1ff9b 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -24,6 +24,7 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useFilesValidation from '@hooks/useFilesValidation'; +import useIOUUtils from '@hooks/useIOUUtils'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; @@ -38,7 +39,7 @@ import getPlatform from '@libs/getPlatform'; import type Platform from '@libs/getPlatform/types'; import getReceiptsUploadFolderPath from '@libs/getReceiptsUploadFolderPath'; import HapticFeedback from '@libs/HapticFeedback'; -import {navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils'; +import {navigateToParticipantPage} from '@libs/IOUUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; @@ -107,7 +108,6 @@ function IOURequestStepScan({ const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${initialTransactionID}`, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); - const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: (val) => val?.reports}); @@ -118,6 +118,7 @@ function IOURequestStepScan({ const [didCapturePhoto, setDidCapturePhoto] = useState(false); const [shouldShowMultiScanEducationalPopup, setShouldShowMultiScanEducationalPopup] = useState(false); const [cameraKey, setCameraKey] = useState(0); + const {shouldStartLocationPermissionFlow} = useIOUUtils(); const defaultTaxCode = getDefaultTaxCode(policy, initialTransaction); const transactionTaxCode = (initialTransaction?.taxCode ? initialTransaction?.taxCode : defaultTaxCode) ?? ''; @@ -576,7 +577,7 @@ function IOURequestStepScan({ setReceiptFiles(newReceiptFiles); const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); @@ -594,7 +595,7 @@ function IOURequestStepScan({ if (shouldSkipConfirmation) { const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); return; @@ -603,7 +604,7 @@ function IOURequestStepScan({ } navigateToConfirmationStep(files, false); }, - [initialTransaction, iouType, navigateToConfirmationStep, shouldSkipConfirmation, lastLocationPermissionPrompt], + [shouldSkipConfirmation, navigateToConfirmationStep, initialTransaction, iouType, shouldStartLocationPermissionFlow], ); const capturePhoto = useCallback(() => { diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 763a6c507c141..578d3b7bf5d7d 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -26,6 +26,7 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useFilesValidation from '@hooks/useFilesValidation'; +import useIOUUtils from '@hooks/useIOUUtils'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; @@ -38,7 +39,7 @@ import {dismissProductTraining} from '@libs/actions/Welcome'; import {isMobile, isMobileWebKit} from '@libs/Browser'; import {base64ToFile, isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; -import {navigateToParticipantPage, shouldStartLocationPermissionFlow} from '@libs/IOUUtils'; +import {navigateToParticipantPage} from '@libs/IOUUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; @@ -115,11 +116,11 @@ function IOURequestStepScan({ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: false}); const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); - const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: (val) => val?.reports}); const isEditing = action === CONST.IOU.ACTION.EDIT; const canUseMultiScan = !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport; const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo); + const {shouldStartLocationPermissionFlow} = useIOUUtils(); const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, { selector: (items) => Object.values(items ?? {}), @@ -609,7 +610,7 @@ function IOURequestStepScan({ setReceiptFiles(newReceiptFiles); const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && files.length; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); @@ -662,7 +663,7 @@ function IOURequestStepScan({ if (shouldSkipConfirmation) { const gpsRequired = initialTransaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT; if (gpsRequired) { - const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(lastLocationPermissionPrompt); + const beginLocationPermissionFlow = shouldStartLocationPermissionFlow(); if (beginLocationPermissionFlow) { setStartLocationPermissionFlow(true); return; @@ -671,7 +672,7 @@ function IOURequestStepScan({ } navigateToConfirmationStep(files, false); }, - [initialTransaction, iouType, lastLocationPermissionPrompt, navigateToConfirmationStep, shouldSkipConfirmation], + [initialTransaction, iouType, shouldStartLocationPermissionFlow, navigateToConfirmationStep, shouldSkipConfirmation], ); const getScreenshot = useCallback(() => { diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index fdf50f830ae12..acbf0e0c4408e 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -460,37 +460,3 @@ describe('Check valid amount for IOU/Expense request', () => { expect(unreportedAmount).toBeLessThan(0); }); }); - -describe('shouldStartLocationPermissionFlow', () => { - const now = new Date(); - const daysAgo = (days: number) => { - const d = new Date(now); - d.setDate(d.getDate() - days); - return d.toISOString(); - }; - - test('returns true if lastLocationPermissionPrompt is undefined', () => { - expect(IOUUtils.shouldStartLocationPermissionFlow(undefined)).toBe(true); - }); - - test('returns true if lastLocationPermissionPrompt is empty string', () => { - expect(IOUUtils.shouldStartLocationPermissionFlow('')).toBe(true); - }); - - test('returns false if lastLocationPermissionPrompt is a valid date string within threshold', () => { - // Use a date string less than threshold days ago - const recentDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS - 1); - expect(IOUUtils.shouldStartLocationPermissionFlow(recentDate)).toBe(false); - }); - - test('returns true if lastLocationPermissionPrompt is a valid date string outside threshold', () => { - // Use a date string more than threshold days ago - const oldDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS + 1); - expect(IOUUtils.shouldStartLocationPermissionFlow(oldDate)).toBe(true); - }); - - test('returns false if lastLocationPermissionPrompt is an invalid date string', () => { - // Should not start flow if the string is not a valid date - expect(IOUUtils.shouldStartLocationPermissionFlow('not-a-date')).toBe(false); - }); -}); diff --git a/tests/unit/useIOUUtilsTest.ts b/tests/unit/useIOUUtilsTest.ts new file mode 100644 index 0000000000000..eea97816e0d3c --- /dev/null +++ b/tests/unit/useIOUUtilsTest.ts @@ -0,0 +1,117 @@ +import {renderHook} from '@testing-library/react-native'; +import Onyx from 'react-native-onyx'; +import DateUtils from '@libs/DateUtils'; +import CONST from '@src/CONST'; +import useIOUUtils from '@src/hooks/useIOUUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +describe('useIOUUtils', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + beforeEach(async () => { + await Onyx.clear(); + }); + + describe('shouldStartLocationPermissionFlow', () => { + const now = new Date(); + const daysAgo = (days: number) => { + const d = new Date(now); + d.setDate(d.getDate() - days); + return d.toISOString(); + }; + + it('returns true when lastLocationPermissionPrompt is undefined', async () => { + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(true); + }); + + it('returns true when lastLocationPermissionPrompt is null', async () => { + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, null); + await waitForBatchedUpdatesWithAct(); + + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(true); + }); + + it('returns true when lastLocationPermissionPrompt is empty string', async () => { + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, ''); + await waitForBatchedUpdatesWithAct(); + + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(true); + }); + + it('returns false when lastLocationPermissionPrompt is a valid date string within threshold', async () => { + const recentDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS - 1); + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, recentDate); + await waitForBatchedUpdatesWithAct(); + + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(false); + }); + + it('returns true when lastLocationPermissionPrompt is a valid date string outside threshold', async () => { + const oldDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS + 1); + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, oldDate); + await waitForBatchedUpdatesWithAct(); + + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(true); + }); + + it('returns false when lastLocationPermissionPrompt is an invalid date string', async () => { + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, 'not-a-date'); + await waitForBatchedUpdatesWithAct(); + + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(false); + }); + + it('returns false when lastLocationPermissionPrompt is exactly at threshold', async () => { + const thresholdDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS); + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, thresholdDate); + await waitForBatchedUpdatesWithAct(); + + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(false); + }); + + it('reacts to changes in lastLocationPermissionPrompt', async () => { + const {result} = renderHook(() => useIOUUtils()); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(true); + + const recentDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS - 1); + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, recentDate); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(false); + + const oldDate = daysAgo(CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS + 1); + Onyx.set(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, oldDate); + await waitForBatchedUpdatesWithAct(); + + expect(result.current.shouldStartLocationPermissionFlow()).toBe(true); + }); + }); +}); From 363fe155e09c02dce305293efd871dc3e7753826 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Mon, 28 Jul 2025 23:57:45 +0700 Subject: [PATCH 10/12] update --max-warnings --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29d6ba86eadb6..0a18c881e7759 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=326 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=324 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", From c272d929ae1601afae770f05235ff7d85e5683b4 Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 29 Jul 2025 00:01:20 +0700 Subject: [PATCH 11/12] eslint failed --- src/libs/IOUUtils.ts | 1 - tests/unit/useIOUUtilsTest.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 65d0a7af29197..8c7ff76881395 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -6,7 +6,6 @@ import type {OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; import type {IOURequestType} from './actions/IOU'; import {getCurrencyUnit} from './CurrencyUtils'; -import DateUtils from './DateUtils'; import Navigation from './Navigation/Navigation'; import Performance from './Performance'; import {getReportTransactions} from './ReportUtils'; diff --git a/tests/unit/useIOUUtilsTest.ts b/tests/unit/useIOUUtilsTest.ts index eea97816e0d3c..fd02258e02dc3 100644 --- a/tests/unit/useIOUUtilsTest.ts +++ b/tests/unit/useIOUUtilsTest.ts @@ -1,6 +1,5 @@ import {renderHook} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; -import DateUtils from '@libs/DateUtils'; import CONST from '@src/CONST'; import useIOUUtils from '@src/hooks/useIOUUtils'; import ONYXKEYS from '@src/ONYXKEYS'; From 18ab48ac215f15d270e3cd0529440c9d34e6472f Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Tue, 29 Jul 2025 11:03:51 +0700 Subject: [PATCH 12/12] update --max-warnings --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 299faf2b3e449..4be5ad715abd1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=320 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=322 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh",