Skip to content

Commit cbd3dc0

Browse files
committed
fix: add double-submit guard, userLocation shortcut, isConfirming state
1 parent c1b3f70 commit cbd3dc0

1 file changed

Lines changed: 48 additions & 20 deletions

File tree

src/pages/Share/SubmitDetailsPage.tsx

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {StackScreenProps} from '@react-navigation/stack';
22
import {hasSeenTourSelector} from '@selectors/Onboarding';
33
import {validTransactionDraftsSelector} from '@selectors/TransactionDraft';
4-
import React, {useCallback, useEffect, useMemo, useState} from 'react';
4+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
55
import {View} from 'react-native';
66
import type {OnyxEntry} from 'react-native-onyx';
77
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -102,6 +102,10 @@ function SubmitDetailsPage({
102102
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
103103
const personalPolicy = usePersonalPolicy();
104104
const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
105+
const [selectedParticipantList, setSelectedParticipantList] = useState<Participant[]>([]);
106+
const [isConfirming, setIsConfirming] = useState(false);
107+
const formHasBeenSubmitted = useRef(false);
108+
const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION);
105109

106110
const [errorTitle, setErrorTitle] = useState<string | undefined>(undefined);
107111
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
@@ -294,52 +298,69 @@ function SubmitDetailsPage({
294298
const onSuccess = (participant: Participant, file: File, locationPermissionGranted?: boolean) => {
295299
const receipt: Receipt = file;
296300
receipt.state = file && CONST.IOU.RECEIPT_STATE.SCAN_READY;
297-
if (locationPermissionGranted) {
298-
getCurrentPosition(
299-
(successData) => {
300-
finishRequestAndNavigate(participant, receipt, {
301-
lat: successData.coords.latitude,
302-
long: successData.coords.longitude,
303-
});
304-
},
305-
(errorData) => {
306-
Log.info('[SubmitDetailsPage] getCurrentPosition failed', false, errorData);
307-
finishRequestAndNavigate(participant, receipt);
308-
},
309-
);
301+
if (!locationPermissionGranted) {
302+
finishRequestAndNavigate(participant, receipt);
303+
return;
304+
}
305+
// Use cached userLocation when available — avoids an extra getCurrentPosition round-trip.
306+
if (userLocation) {
307+
finishRequestAndNavigate(participant, receipt, {
308+
lat: userLocation.latitude,
309+
long: userLocation.longitude,
310+
});
310311
return;
311312
}
312-
finishRequestAndNavigate(participant, receipt);
313+
getCurrentPosition(
314+
(successData) => {
315+
finishRequestAndNavigate(participant, receipt, {
316+
lat: successData.coords.latitude,
317+
long: successData.coords.longitude,
318+
});
319+
},
320+
(errorData) => {
321+
Log.info('[SubmitDetailsPage] getCurrentPosition failed', false, errorData);
322+
finishRequestAndNavigate(participant, receipt);
323+
},
324+
);
313325
};
314326

315327
// Extracted from onConfirm — re-entering onConfirm from the permission modal deadlocked when OS permission was pre-granted.
316328
const performUpload = (participant: Participant, locationPermissionGranted: boolean) => {
317-
if (!currentAttachment) {
329+
if (formHasBeenSubmitted.current || !currentAttachment) {
330+
setIsConfirming(false);
318331
return;
319332
}
333+
formHasBeenSubmitted.current = true;
320334
readFileAsync(
321335
currentReceiptSource,
322336
currentReceiptName,
323337
(file) => onSuccess(participant, file, locationPermissionGranted),
324-
() => {},
338+
() => {
339+
// Allow retry after a file-read failure.
340+
formHasBeenSubmitted.current = false;
341+
setIsConfirming(false);
342+
},
325343
currentReceiptType,
326344
);
327345
};
328346

329347
const onConfirm = (listOfParticipants?: Participant[], gpsRequired?: boolean) => {
348+
setIsConfirming(true);
330349
const shouldStartLocationPermissionFlow =
331350
gpsRequired &&
332351
(!lastLocationPermissionPrompt ||
333352
(DateUtils.isValidDateString(lastLocationPermissionPrompt ?? '') &&
334353
DateUtils.getDifferenceInDaysFromNow(new Date(lastLocationPermissionPrompt ?? '')) > CONST.IOU.LOCATION_PERMISSION_PROMPT_THRESHOLD_DAYS));
335354

336355
if (shouldStartLocationPermissionFlow) {
356+
setSelectedParticipantList(listOfParticipants ?? selectedParticipants);
337357
setStartLocationPermissionFlow(true);
338358
return;
339359
}
340360

341361
const participant = listOfParticipants?.at(0) ?? selectedParticipants.at(0);
342362
if (!participant) {
363+
setIsConfirming(false);
343364
return;
344365
}
345366
performUpload(participant, false);
@@ -362,24 +383,30 @@ function SubmitDetailsPage({
362383
/>
363384
<LocationPermissionModal
364385
startPermissionFlow={startLocationPermissionFlow}
365-
resetPermissionFlow={() => setStartLocationPermissionFlow(false)}
386+
resetPermissionFlow={() => {
387+
setStartLocationPermissionFlow(false);
388+
setIsConfirming(false);
389+
}}
366390
onGrant={() => {
367391
setStartLocationPermissionFlow(false);
368-
const participant = selectedParticipants.at(0);
392+
const participant = selectedParticipantList.at(0) ?? selectedParticipants.at(0);
369393
if (!participant) {
394+
setIsConfirming(false);
370395
return;
371396
}
372397
navigateAfterInteraction(() => performUpload(participant, true));
373398
}}
374399
onDeny={() => {
375400
updateLastLocationPermissionPrompt();
376401
setStartLocationPermissionFlow(false);
377-
const participant = selectedParticipants.at(0);
402+
const participant = selectedParticipantList.at(0) ?? selectedParticipants.at(0);
378403
if (!participant) {
404+
setIsConfirming(false);
379405
return;
380406
}
381407
navigateAfterInteraction(() => performUpload(participant, false));
382408
}}
409+
onInitialGetLocationCompleted={() => setIsConfirming(false)}
383410
/>
384411
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone]}>
385412
<MoneyRequestConfirmationList
@@ -390,6 +417,7 @@ function SubmitDetailsPage({
390417
onToggleReimbursable={setReimbursable}
391418
isPolicyExpenseChat={isPolicyExpenseChat}
392419
policyID={policy?.id}
420+
isConfirming={isConfirming}
393421
onConfirm={(updatedParticipants) => onConfirm(updatedParticipants, true)}
394422
receiptPath={currentReceiptSource}
395423
receiptFilename={currentReceiptName}

0 commit comments

Comments
 (0)